Haskell getContents 等待 EOF
Haskell getContents wait for EOF
我想等到用户输入以 EOF 终止,然后将其全部输出。这不是getContents
应该做的吗?每次用户点击回车时都会输出以下代码,我做错了什么?
import System.IO
main = do
hSetBuffering stdin NoBuffering
contents <- getContents
putStrLn contents
根本问题是 getContents
是 Lazy IO 的实例。这意味着 getContents
产生一个 thunk,可以像正常 Haskell 值一样进行评估,并且仅在强制 .
时才执行相关的 IO
contents
是 putStr
尝试打印的惰性列表,它会强制列表并导致 getContents
尽可能多地读取。 putStr
然后打印强制的所有内容,并继续尝试强制列表的其余部分,直到它达到 []
。由于 getContents
可以读取越来越多的流——确切的行为取决于缓冲——putStr
可以立即打印越来越多的流,给你你看到的行为。
虽然此行为对于非常简单的脚本很有用,但它会将 Haskell 的评估顺序与 可观察的 效果联系起来——这是它从未打算做的事情。这意味着精确控制 contents
的某些部分何时打印是很尴尬的,因为你必须打破正常的 Haskell 抽象并准确理解事物是如何被评估的。
这会导致一些潜在的非直觉行为。例如,如果您尝试获取输入的长度 - 实际上 使用 它 - 在您开始打印之前强制列表,为您提供您想要的行为:
main = do
contents <- getContents
let n = length contents
print n
putStr contents
但是如果您将 print n
移到 putStr
之后,您会回到原来的行为,因为 n
直到 之后 [=60] 才会被强制=] 打印输入(即使在使用 putStr
之前 n
仍然得到 defined:
main = do
contents <- getContents
let n = length contents
putStr contents
print n
通常,这种事情不是问题,因为它不会改变代码的行为(尽管它会影响性能)。 Lazy IO只是通过打通抽象层,将其带入了正确的境界。
这也给了我们如何解决您的问题的提示:我们需要某种方式在打印之前强制 contents
。正如我们所看到的,我们可以用 length
来做到这一点,因为 length
需要在计算结果之前遍历整个列表。我们可以使用 seq
代替打印它,这会强制左侧表达式与右侧表达式同时求值,但会丢弃实际值:
main = do
contents <- getContents
let n = length contents
n `seq` putStr contents
同时,这仍然有点难看,因为我们使用 length
只是为了遍历列表,而不是因为我们真的关心它。我们真正想要的是 just 遍历列表足以评估它的函数,而不做任何其他事情。令人高兴的是,这正是 deepseq
所做的(对于许多数据结构,而不仅仅是列表):
import Control.DeepSeq
import System.IO
main = do
contents <- getContents
contents `deepseq` putStr contents
这是懒惰的问题I/O。一种简单的解决方案是使用严格的 I/O,例如通过 ByteStrings:
import qualified Data.ByteString as S
main :: IO ()
main = S.getContents >>= S.putStr
您可以使用 strict
包中的替换函数 (link):
import qualified System.IO.Strict as S
main = do
contents <- S.getContents
putStrLn contents
请注意,读取不需要设置缓冲。缓冲实际上只在写入文件时有用。有关详细信息,请参阅此答案 。
System.IO.Strict中hGetContents
的严格版本的定义很简单:
hGetContents :: IO.Handle -> IO.IO String
hGetContents h = IO.hGetContents h >>= \s -> length s `seq` return s
即,它通过对 hGetContents
的 standard/lazy 版本返回的字符串调用 length
来强制将所有内容读入内存。
我想等到用户输入以 EOF 终止,然后将其全部输出。这不是getContents
应该做的吗?每次用户点击回车时都会输出以下代码,我做错了什么?
import System.IO
main = do
hSetBuffering stdin NoBuffering
contents <- getContents
putStrLn contents
根本问题是 getContents
是 Lazy IO 的实例。这意味着 getContents
产生一个 thunk,可以像正常 Haskell 值一样进行评估,并且仅在强制 .
contents
是 putStr
尝试打印的惰性列表,它会强制列表并导致 getContents
尽可能多地读取。 putStr
然后打印强制的所有内容,并继续尝试强制列表的其余部分,直到它达到 []
。由于 getContents
可以读取越来越多的流——确切的行为取决于缓冲——putStr
可以立即打印越来越多的流,给你你看到的行为。
虽然此行为对于非常简单的脚本很有用,但它会将 Haskell 的评估顺序与 可观察的 效果联系起来——这是它从未打算做的事情。这意味着精确控制 contents
的某些部分何时打印是很尴尬的,因为你必须打破正常的 Haskell 抽象并准确理解事物是如何被评估的。
这会导致一些潜在的非直觉行为。例如,如果您尝试获取输入的长度 - 实际上 使用 它 - 在您开始打印之前强制列表,为您提供您想要的行为:
main = do
contents <- getContents
let n = length contents
print n
putStr contents
但是如果您将 print n
移到 putStr
之后,您会回到原来的行为,因为 n
直到 之后 [=60] 才会被强制=] 打印输入(即使在使用 putStr
之前 n
仍然得到 defined:
main = do
contents <- getContents
let n = length contents
putStr contents
print n
通常,这种事情不是问题,因为它不会改变代码的行为(尽管它会影响性能)。 Lazy IO只是通过打通抽象层,将其带入了正确的境界。
这也给了我们如何解决您的问题的提示:我们需要某种方式在打印之前强制 contents
。正如我们所看到的,我们可以用 length
来做到这一点,因为 length
需要在计算结果之前遍历整个列表。我们可以使用 seq
代替打印它,这会强制左侧表达式与右侧表达式同时求值,但会丢弃实际值:
main = do
contents <- getContents
let n = length contents
n `seq` putStr contents
同时,这仍然有点难看,因为我们使用 length
只是为了遍历列表,而不是因为我们真的关心它。我们真正想要的是 just 遍历列表足以评估它的函数,而不做任何其他事情。令人高兴的是,这正是 deepseq
所做的(对于许多数据结构,而不仅仅是列表):
import Control.DeepSeq
import System.IO
main = do
contents <- getContents
contents `deepseq` putStr contents
这是懒惰的问题I/O。一种简单的解决方案是使用严格的 I/O,例如通过 ByteStrings:
import qualified Data.ByteString as S
main :: IO ()
main = S.getContents >>= S.putStr
您可以使用 strict
包中的替换函数 (link):
import qualified System.IO.Strict as S
main = do
contents <- S.getContents
putStrLn contents
请注意,读取不需要设置缓冲。缓冲实际上只在写入文件时有用。有关详细信息,请参阅此答案
System.IO.Strict中hGetContents
的严格版本的定义很简单:
hGetContents :: IO.Handle -> IO.IO String
hGetContents h = IO.hGetContents h >>= \s -> length s `seq` return s
即,它通过对 hGetContents
的 standard/lazy 版本返回的字符串调用 length
来强制将所有内容读入内存。