如何理解Haskell中的流

时间:2016-02-28 14:24:25

标签: haskell stream functional-programming

我正在阅读“让你学习哈斯克尔的伟大成就”第9章:输入和输出。有一个用于解释流的示例代码:

main = do   
  withFile "something.txt" ReadMode (\handle -> do  
    contents <- hGetContents handle  
    putStr contents) 

书中说:

  

这就是为什么在这种情况下它实际读取一行,将其打印到   输出,读取下一行,打印它等等。

但在之前的内容中,对于同一个例子,它也说:

  

这真的很酷,因为我们可以将内容视为整个内容   该文件,但它并没有真正加载到内存中。

我是函数式编程的新手,我对此非常困惑,为什么我们可以将contents作为整个内容,如果它一次读取一行?我虽然contents中的contents <- hGetContents handle只是一行的内容,但Haskell是否将每行的内容保存到临时内存或其他内容中?

3 个答案:

答案 0 :(得分:2)

  

为什么我们可以将内容作为整个内容,如果它一次读取一行?

首先请注意,没有必要逐行读取内容(尽管有可能,我稍后会介绍)。作者的意思是,即使整个文件没有加载到内存中,您也可以在概念上假设变量contents具有文件的全部内容。这是可能的,因为文件的延迟流式传输(如果您更感兴趣,可以看到source以查看低级别详细信息。它基本上使用unsafeInterleaveIO来实现此目的。

  

Haskell会将每行的内容保存到临时内存或其他内容中吗?

这取决于所使用的缓冲类型。根据文档,它取决于底层文件系统:

  

打开句柄时的默认缓冲模式是   依赖于实现,可能依赖于文件系统对象   附在那个手柄上的。对于大多数实现,物理   文件通常是块缓冲的,终端通常是   行缓冲。

但您可以使用hGetBuffering :: Handle -> IO BufferMode来了解自己所处的缓冲模式。

答案 1 :(得分:2)

  

如何理解Haskell中的流

您可以将视为一个函数,在调用时,返回结果的部分不是全部)以及回调功能以在需要时获取剩余部分。因此从技术上讲,它会为您提供整个内容,但一次只能提供一个块,并且只有在您要求其余内容时才会这样。

如果Haskell没有non-strict semantics,你可以通过以下方式实现这个概念:

data Stream a = Stream [a] (() -> Stream a)

instance (Show a) => Show (Stream a) where
    show (Stream xs _) = show xs ++ " ..."

rest :: Stream a -> Stream a -- ask for the rest of the stream
rest (Stream _ f) = f ()

然后说你想要一个迭代整数的流。你可以返回前3个并推迟其余的直到用户要求它:

iter :: Int -> Stream Int
iter x = Stream [x, x + 1, x + 2] (\_ -> iter (x + 3))

然后,

\> iter 0
[0,1,2] ...

但是,如果你继续要求其余的,你会得到整个内容

\> take 5 $ iterate rest (iter 0)
[[0,1,2] ...,[3,4,5] ...,[6,7,8] ...,[9,10,11] ...,[12,13,14] ...]

\> let go (Stream [i, j, k] _) acc = i:j:k:acc
\> take 20 . foldr go [] $ iterate rest (iter 0)
[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]

线缓冲相同的故事。它读取并返回第一行,但是你可以要求下一行,下一行,......所以从技术上讲,即使它一次只读取一行,你也可以得到整个内容。

答案 2 :(得分:0)

请注意,hGetHandle本身从不读取任何内容;它只返回一个IO动作,当最终执行时,它将从文件中读取。当putStrhGetContents handle >>= putStr(简称为do符号desugared)结合使用时,您将获得一个IO操作,该操作将获取句柄并将其内容输出到屏幕。在Haskell程序本身中,您从未指定 如何发生;它完全取决于Haskell运行时以及它如何执行您创建的IO操作。