我正在阅读“让你学习哈斯克尔的伟大成就”第9章:输入和输出。有一个用于解释流的示例代码:
main = do
withFile "something.txt" ReadMode (\handle -> do
contents <- hGetContents handle
putStr contents)
书中说:
这就是为什么在这种情况下它实际读取一行,将其打印到 输出,读取下一行,打印它等等。
但在之前的内容中,对于同一个例子,它也说:
这真的很酷,因为我们可以将内容视为整个内容 该文件,但它并没有真正加载到内存中。
我是函数式编程的新手,我对此非常困惑,为什么我们可以将contents
作为整个内容,如果它一次读取一行?我虽然contents
中的contents <- hGetContents handle
只是一行的内容,但Haskell是否将每行的内容保存到临时内存或其他内容中?
答案 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
动作,当最终执行时,它将从文件中读取。当putStr
与hGetContents handle >>= putStr
(简称为do
符号desugared)结合使用时,您将获得一个IO操作,该操作将获取句柄并将其内容输出到屏幕。在Haskell程序本身中,您从未指定 如何发生;它完全取决于Haskell运行时以及它如何执行您创建的IO
操作。