Haskell中的递归IO

时间:2011-05-24 16:24:39

标签: haskell recursion io monads lazy-evaluation

在Haskell中,我可以轻松定义一个递归函数,它接受一个值并返回一个字符串:

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

我想使用相同类型的设计从文件句柄中读取可用数据。在这种特殊情况下,我需要以与hGetContents相同的方式读取数据,但不要将句柄保留在“半封闭”状态,这样我就可以循环与使用createProcess打开的进程的stdin / stdout句柄的交互: / p>

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。 TIA!

3 个答案:

答案 0 :(得分:10)

天真的解决方案,严格和 O(n)堆栈

您仍然必须使用 do -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 :(得分:6)

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

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

这意味着您只能将列表附加在一起(请记住String[Char]的别名,因此它是一个列表)。 hGetLine的类型为Handle -> IO StringhGetLines的类型应为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)。或者您可以使用{{1}}打印每一行。

答案 2 :(得分:-1)

这表示部分代码希望hGetLines h具有类型IO a,而另一部分则认为它具有类型[a]。您可能希望if语句为:

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