2011-05-24 87 views
5

在Haskell,我可以很容易地定義一個遞歸函數,它接受一個值,並返回一個字符串:遞歸IO在Haskell

Prelude> let countdown i = if (i > 0) then (show i) ++ countdown (i-1) else "" 
Prelude> countdown 5 
"54321" 

我想用同一種設計,從文件句柄讀取可用的數據。在這種特殊情況下,我需要以與hGetContents相同的方式讀取數據,但不要將句柄置於「半閉合」狀態,以便可以使用createProcess打開的進程的stdin/stdout句柄循環交互:

main = do 
    -- do work to get hin/hout handles for subprocess input/output 

    hPutStrLn hin "whats up?" 

    -- works 
    -- putStrLn =<< hGetContents hout 

    putStrLn =<< hGetLines hout 

    where 
     hGetLines h = do 
      readable <- hIsReadable h 
      if readable 
       then hGetLine h ++ hGetLines h 
       else [] 

給出了錯誤:

Couldn't match expected type `IO b0' with actual type `[a0]' 
In the expression: hGetLine h : hGetLines h 

我知道有可用於完成我想要完成各種庫,但SICE我學習,我的問題是如何真正執行遞歸IO。 TIA!

回答

10

天真的解決方案,嚴格O(n)的堆棧

你仍然必須使用 -notation,這將導致此:

import System.IO 
import System.IO.Unsafe (unsafeInterleaveIO) 

-- Too strict! 
hGetLines :: Handle -> IO [String] 
hGetLines h = do 
    readable <- hIsReadable h 
    if readable 
     then do 
      x <- hGetLine h 
      xs <- hGetLines h 
      return (x:xs) 
     else return [] 

但看到我評論,這個版本的hGetLines太嚴格了!

懶惰,流版本

它不會返回列表,直到它有所有的輸入。你需要一些比較懶的東西。對於這一點,我們有unsafeInterleaveIO

-- Just right 
hGetLines' :: Handle -> IO [String] 
hGetLines' h = unsafeInterleaveIO $ do 
    readable <- hIsReadable h 
    if readable 
     then do 
      x <- hGetLine h 
      xs <- hGetLines' h 
      return (x:xs) 
     else return [] 

現在你可以開始串流結果行由線到你的消費者代碼:

*Main> hGetLines' stdin 
123 
["123"345 
,"345"321 
,"321"^D^CInterrupted. 
-1

這就是說部分代碼希望hGetLines h的類型爲IO a,而另一部分代碼發現它的類型爲[a]。你可能希望你的if語句是:

if readable 
    then return hGetLine h ++ hGetLines h 
    else return [] 
+2

你的代碼是有些奇怪......它甚至沒有編譯。那麼如何:'如果可讀然後hGetLine >> = \ a - > hGetLine >> = \ b - >返回$ a + b else返回[]'?另一個問題是,這不是流。 – fuz 2011-05-24 16:32:48

6

如果選中的(++)在ghci的類型,你可以:

Prelude> :t (++) 
(++) :: [a] -> [a] -> [a] 

這意味着你只能追加名單在一起(請記住,String是一個別名[Char],所以它是一個列表)。 hGetLine的類型是Handle -> IO String,而hGetLines的類型應該是IO [String]因此,您不能附加這些值。 (:)的型號爲a -> [a],在這裏效果更好。

if readable 
    then do 
    -- First you need to extract them 
    a <- hGetLine h 
    b <- hGetLines h 
    -- a and b have type String 
    -- Now we can cons them and then go back into IO 
    return (a : b) 

這同樣適用於else []。您需要返回IO [String]類型的值。將其更改爲return []

而且,你將不能夠只是putStrLn行,因爲(=<< hGetLines h)給你[String],而不是String這是什麼putStrLn期待。 這可以通過幾種方式解決。一個是首先將值連接起來。 putStrln . concat =<< (hGetLines h)。或者您可以使用mapM_ putStrLn (hGetLines h)打印每一行。

+0

您是否打算在第二次電話中調用'hGetLines'? – 2011-05-24 16:45:01

+0

Woops。錯過了遞歸調用,所以應該使用':'來代替。 – 2011-05-24 16:49:10

+0

請注意,這個例子不會流,並使用* O(n)*堆棧。 – 2011-05-24 18:12:47