【问题标题】:Recursive IO in HaskellHaskell 中的递归 IO
【发布时间】:2011-09-01 02:24:25
【问题描述】:

在 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

我知道有各种库可用于完成我想要完成的工作,但是我正在学习我的问题实际上是如何执行递归 IO。蒂亚!

【问题讨论】:

    标签: haskell recursion io monads lazy-evaluation


    【解决方案1】:

    朴素解决方案,严格和O(n)堆栈

    您仍然必须使用 do 表示法,这会导致:

    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.
    

    【讨论】:

      【解决方案2】:

      如果你在 ghci 中检查 (++) 的类型,你会得到:

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

      意味着您只能将列表附加在一起(请记住,String[Char] 的别名,所以它是一个列表)。 hGetLine 的类型是Handle -&gt; IO StringhGetLines 的类型应该是IO [String] 所以你不能附加这些值。 (:) 的类型为 a -&gt; [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 行,因为(=&lt;&lt; hGetLines h) 给您[String] 而不是String,这是putStrLn 所期望的。 这可以通过多种方式解决。一个是首先连接值。 putStrln . concat =&lt;&lt; (hGetLines h)。或者您可以使用mapM_ putStrLn (hGetLines h) 打印每一行。

      【讨论】:

      • 你的意思是在第二次通话中拨打hGetLines 吗?
      • 糟糕。错过了递归调用,所以应该改用:
      • 请注意,此示例不会流式传输,并且使用 O(n) 堆栈。
      【解决方案3】:

      这表示部分代码期望 hGetLines h 的类型为 IO a,而另一部分发现它的类型为 [a]。你可能希望你的 if 语句是:

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

      【讨论】:

      • 你的代码有点奇怪......它甚至没有编译。这个怎么样:if readable then hGetLine &gt;&gt;= \a -&gt; hGetLine &gt;&gt;= \b -&gt; return $ a + b else return []?另一个问题是,这不会流式传输。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-03-26
      • 2013-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多