2011-05-24 119 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