2012-09-11 61 views
3

我寫了這個代碼:迭代一個裏面做阻塞

toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
        outputFile <- openFile "couples.txt" WriteMode 
        readAndChange inputFile outputFile 

readAndChange i o = do iseof <- hIsEOF i 
         if iseof then (return o) 
         else do line <- hGetLine i 
           hPutStrLn o (show (extractNameAndId line)) 
           readAndChange i o 

我不知道如果我可以只使用一個功能重寫這段代碼,使用類似這種模式的東西:

function x = do ... 
       label 
       ..... 
       if ... then label else exit 

回答

12

你通過編程以不必要的方式讓生活變得困難。您使用美麗的Haskell語言進行編程,並且您正在尋找goto結構!

爲什麼不只是import Control.Applicative (<$>)

readAndChange' = writeFile "couples.txt" =<< 
    unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv" 

(是啊,這幾乎是一個需要一行代碼。這是在清潔,實用的風格,整潔的讀取和寫入行的機制。儘可能的處理是在純代碼完成,僅輸入和輸出是基於IO)

說明:

這裏unlines.map (show.extractNameAndId).lines通過切碎我處理您輸入t分爲行,將extractNameAndId然後show應用於每個使用map,然後再將它們與unlines再次連接在一起。

unlines.map (show.extractNameAndId).lines <$> readFile "deletedId.csv"將讀取該文件並應用處理功能。對於fmap<$>是令人愉快的語法。

writeFile "couples.txt" =<< getanswergetanswer >>= writeFile "couples.txt"相同 - 如上獲得答案,然後將其寫入文件。

試着寫greet xs = "hello " ++ xs然後在ghci中做這些的樂趣

greet "Jane"  -- apply your pure function purely 
greet $ "Jane"  -- apply it purely again 
greet <$> ["Jane","Craig","Brian"] -- apply your function on something that produces three names 
greet <$> Just "Jane"    -- apply your function on something that might have a name 
greet <$> Nothing     -- apply your function on something that might have a name 
greet <$> getLine     -- apply your function to whatever you type in 
greet <$> readFile "deletedId.csv" -- apply your function to your file 

最後一個是我們如何在readAndChange使用<$>。如果有大量的數據在 deletedId.csv你會錯過打招呼,當然,你可以做

greet <$> readFile "deletedId.csv" >>= writeFile "hi.txt" 
take 4.lines <$> readFile "hi.txt" 

看到的第4行。

因此$可以讓你在你給它的參數上使用你的函數。 greet :: String -> String所以,如果你寫greet $ person,該person必須是String類型,而如果你寫greet <$> someone,該someone可以是任何產生String - 字符串列表,一個IO String,一個Maybe String。從技術上講,someone :: Applicative f => f String,但你應該先閱讀類型類和Applicative Functors。瞭解你對Haskell的好處是一個很好的資源。

爲了更有趣,如果您有一個以上參數的函數,您仍然可以使用可愛的Applicative風格。

insult :: String -> String -> String 
insult a b = a ++ ", you're almost as ugly as " ++ b 

嘗試

insult "Fred" "Barney" 
insult "Fred" $ "Barney" 
insult <$> ["Fred","Barney"] <*> ["Wilma","Betty"] 
insult <$> Just "Fred" <*> Nothing 
insult <$> Just "Fred" <*> Just "Wilma" 
insult <$> readFile "someone.txt" <*> readFile "someoneElse.txt" 

在這裏,你需要的參數之間的功能和<*>後使用<$>。它的工作原理首先是令人興奮的,但它是編寫有效計算的最實用的風格。

下一個閱讀關於Applicative Functors。他們很棒。
http://learnyouahaskell.com/functors-applicative-functors-and-monoids
http://en.wikibooks.org/wiki/Haskell/Applicative_Functors

+0

令人驚歎的:)你可以猜到我是一個哈斯克爾新手;如果'$'和'<$>'之間有任何區別,你能解釋我嗎? – Aslan986

+0

@ Aslan986我現在增加了一點。 – AndrewC

+0

不可能不接受你的回答。非常感謝Andrew。 – Aslan986

3
import Control.Monad 
import Control.Monad.Trans 
import Control.Monad.Trans.Either 

readAndChange i o = do 
    result <- fmap (either id id) $ runEitherT $ forever $ do 
     iseof <- lift $ hIsEof i 
     when iseof $ left o -- Break and return 'o' 
     line <- lift $ hGetLine i 
     lift $ hPutStrLn o $ show $ extractNameAndId line 
    -- 'result' now contains the value of 'o' you ended on 
    doSomeWithingWith result 

要理解此技術的工作原理,請閱讀this

3

您可以通過使用let模式,然而這類似於分別定義遞歸函數來完成遞歸:

main = do 
    let x = 10 
    let loop = do 
     print 1 
     when (x<20) loop 
    loop 

您可以使用fixControl.Monad.Fix也實現類似的行爲

main = do 
    let x = 10 
    fix $ \loop -> do 
     print 1 
     when (x<20) loop 

什麼你陳述是種goto label模式。我不知道你是否可以實現這種行爲,但以上使用fixlet可以很容易地幫助你實現遞歸。

[編輯] 有一些更多的模式,以實現像使用Cont單子如

getCC' :: MonadCont m => a -> m (a, a -> m b) 
getCC' x0 = callCC (\c -> let f x = c (x, f) in return (x0, f)) 

main = (`runContT` return) $ do 
    (x, loopBack) <- getCC' 0 
    lift (print x) 
    when (x < 10) (loopBack (x + 1)) 
    lift (putStrLn "finish") 

將從1 to 10打印號碼類似的結果。有關更好的解釋,請參閱goto using continuation

還有一個Goto monad和變壓器。我沒有用過它。您可能會發現它適合您的需求。

+1

我個人非常喜歡'fix'方法。請注意,它在道德上等價於「recusive'let'」方法,因爲'fix'是定義遞歸的一種方法。 (實際上,我只是切換了一些代碼來使用'fix'而不是遞歸'let'。)另外,請注意''Data.Function'中也有'fix';沒有關於它的單子。 'Control.Monad.Fix' *也*提供'MonadFix'類的類,它提供'mfix :: MonadFix m =>(a - > m a) - > m a'。 –

+1

對於任何喜歡「循環組合」方法的人來說,你可以找到更復雜的循環,而不僅僅是[monad-loops'包中的'fix](http://hackage.haskell.org/package/monad-loops) 。它非常整齊。 –

0

不像命令式編程語言,也不像其他函數式編程語言,哈斯克爾包括編寫for循環或while循環,這是你似乎詢問這裏沒有什麼語法結構。

這個想法是遞歸過程和迭代過程可以通過遞歸函數統一捕獲。只是迭代過程被捕獲爲特定類型的遞歸函數:這些函數是尾遞歸。例如在do-blocks內出現的命令代碼也不例外。您可能會發現缺乏顯式循環構造煩人,因爲您必須爲每個循環定義一個新函數,因此在某種意義上必須命名循環。然而,這是一個很小的代價,Haskell的方法的一致性和簡單性,原因有三:

  1. 你不必定義代表了頂級的循環功能。你可以在本地定義它。

  2. 在Haskell中,許多人通常對這些類型的循環使用相同的名稱。這裏最受歡迎的選擇是goaux。你的代碼可以因此被改寫爲:

    toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
            outputFile <- openFile "couples.txt" WriteMode 
            let go = do 
             iseof <- hIsEOF inputFile 
             if iseof then (return outputFile) 
             else do line <- hGetLine inputFile 
               hPutStrLn outputFile (show (extractNameAndId line)) 
               go 
            go 
    
  3. 最後,缺乏循環結構是非常不重要的,因爲很多時候,我們並不需要寫循環的。在你的情況下,這個線程中的其他答案已經顯示了很多方法。

1

你應該做的第一件事就是閱讀有關Control.Monad模塊,它是用於編寫Haskell代碼是絕對必要的文檔。當你在這裏時,安裝Hackage的Control.Monad.Loops包,並閱讀關於它的文檔;你可能會在whileM_功能有特別感興趣:

import Data.Functor ((<$>) 
import Control.Monad.Loops (whileM_) 

readAndChange i o = 
    whileM_ notEOF $ do line <- hGetLine i 
         hPutStrLn o (show (extractNameAndId line)) 
     where notEOF = not <$> (hIsEOF i) 

有問題的庫實現whileM_這樣的,這是你要找的模式:

-- |Execute an action repeatedly as long as the given boolean expression 
-- returns True. The condition is evaluated before the loop body. 
-- Discards results. 
whileM_ :: (Monad m) => m Bool -> m a -> m() 
whileM_ p f = do 
     x <- p 
     if x 
       then do 
         f 
         whileM_ p f 
       else return() 

不過,我不得不同意你以過分迫切的方式寫這篇文章。試着這樣想:你的程序基本上是將輸入字符串轉換爲輸出字符串。這直接意味着你的程序邏輯的核心,應該有這種類型的:

transformInput :: String -> String 
transformInput = ... 

你在一行接一行的基礎改造收益。這意味着你可以細化草圖這樣(的lines功能將字符串分割成線; unlines重新加入列表):

transformInput :: String -> String 
transformInput input = unlines (map transformLine (lines input)) 

transformLine :: String -> String 
transformLine line = show (extractNameAndId line) 

現在你已經得到了在transformInput功能邏輯的核心,所以你只要需要以某種方式將其掛鉤到輸入和輸出句柄。如果你正在處理stdin和stdout,你可以使用interact函數來做到這一點。但是,我們實際上可以竊取其實施和修改:

hInteract  :: Handle -> Handle -> (String -> String) -> IO() 
hInteract i o f = do s <- hGetContents i 
         hPutStr o (f s) 

而現在,瞧:

toCouplesFile = do inputFile <- openFile "deletedId.csv" ReadMode 
        outputFile <- openFile "couples.txt" WriteMode 
        hInteract inputFile outputFile transformInput 

警告:所有未測試的代碼。


最後一兩件事,在充分披露的利益:這裏的技巧是,hGetContents執行懶I/O:它基本上可以讓你把一個手柄的全部內容爲String,從而將transformInput函數應用於句柄的內容。但是這一切都是懶惰的,所以它實際上並不需要一次讀取整個文件。

這是最簡單的方法,你應該學習它,但它有一個很大的弱點,那就是當手柄關閉時你可能失去一些控制權。然而,對於快速和骯髒的程序來說,這是好的。