了解 Haskell 的懒惰
Understanding Haskell's Laziness
我正在阅读:Haskell interact function
所以我尝试了
interact (unlines . map (show . length) . lines)
它按我预期的那样工作。我输入一些东西,按回车键,然后我在提示符下打印出长度。
然后我想尝试让它简单地重复我输入的内容,所以我尝试了
interact (unlines . map id . lines)
但现在它会重复我输入的每个字符。这是为什么?我认为诀窍在于 lines
后跟 unlines
- 但显然不是。 lines "a"
产生 ["a"]
,那么当我开始输入时第一个函数为什么不立即给出“1”作为输出?显然我对 "Finding the length of a string is not like this -- the whole string must be known before any output can be produced."
有一些误解
这与惰性求值有关。我将尝试以尽可能直观的方式解释这一点。
当你写interact (unlines . map (show . length) . lines)
时,每次输入一个字符,我们实际上并不知道下一个输出字符是什么,直到你按下回车。因此,您得到了预期的行为。
但是,在 interact (unlines . map id . lines) = interact id
中的每一点,每次输入一个字符时,都会保证该字符包含在输出中。因此,如果您输入一个字符,该字符也会立即输出。
这是 "lazy" 这个词有点用词不当的原因之一。确实 Haskell 只会在需要时评估某些东西,但另一方面是当需要时,它会尽快这样做。这里 Haskell 需要评估输出,因为你想打印它,所以它会尽可能多地评估它——一次一个字符——具有讽刺意味的是让它看起来很急切!
更具体地说,interact
不是为实时用户输入而设计的——它是为文件输入而设计的,在这种情况下,您可以使用 bash 将文件通过管道传输到可执行文件中。它应该是 运行 像这样的东西:
$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt
如果你想要 line-by-line 缓冲,你可能必须手动完成,除非你想 'trick' 像 Willem 那样的编译器。下面是一些非常简单的代码,可以按您的预期工作——但请注意,它没有退出状态,这与 interact
不同,它将在 EOF 处终止。
main = do
ln <- getLine -- Buffers until you press enter
putStrLn ln -- Print the line we just got
main -- Loop forever
lines "a"
产生 ["a"]
的事实并不意味着如果您当前正在输入 a
,那么 lines
只是处理列表 [=12] 的输入=].您应该将输入视为(可能)无限的字符列表。如果提示正在等待用户输入,则下一次输入时 "blocking"。
但是 not 意味着像 lines
这样的函数可以 not 部分解析结果。 lines
以惰性方式实现,因此它处理字符流,每次看到换行符时,它都会开始发出下一个元素。因此,这意味着 lines
可以将无限的字符序列处理成无限的行列表。
但是,如果您使用 length :: Foldable f => f a -> Int
,则这需要评估列表(但不是列表的 元素 )。因此,这意味着 length
只会在 lines
开始发出下一个项目时发出答案。
您可以使用 seq
(和变体)在完成特定操作之前强制对术语求值。例如 seq :: a -> b -> b
会将第一个参数评估为 弱头部范式 (WHNF),然后 return 第二个参数。
在seq
的基础上,构建了其他功能,如seqList :: [a] -> [a]
in the Data.Lists
module of the lists
包。
我们可以使用它来推迟评估,直到第一行已知,例如:
-- will echo full lines
import Data.Lists(seqList)
interact (unlines . map (\x -> seqList x x) . lines)
我正在阅读:Haskell interact function
所以我尝试了
interact (unlines . map (show . length) . lines)
它按我预期的那样工作。我输入一些东西,按回车键,然后我在提示符下打印出长度。
然后我想尝试让它简单地重复我输入的内容,所以我尝试了
interact (unlines . map id . lines)
但现在它会重复我输入的每个字符。这是为什么?我认为诀窍在于 lines
后跟 unlines
- 但显然不是。 lines "a"
产生 ["a"]
,那么当我开始输入时第一个函数为什么不立即给出“1”作为输出?显然我对 "Finding the length of a string is not like this -- the whole string must be known before any output can be produced."
这与惰性求值有关。我将尝试以尽可能直观的方式解释这一点。
当你写interact (unlines . map (show . length) . lines)
时,每次输入一个字符,我们实际上并不知道下一个输出字符是什么,直到你按下回车。因此,您得到了预期的行为。
但是,在 interact (unlines . map id . lines) = interact id
中的每一点,每次输入一个字符时,都会保证该字符包含在输出中。因此,如果您输入一个字符,该字符也会立即输出。
这是 "lazy" 这个词有点用词不当的原因之一。确实 Haskell 只会在需要时评估某些东西,但另一方面是当需要时,它会尽快这样做。这里 Haskell 需要评估输出,因为你想打印它,所以它会尽可能多地评估它——一次一个字符——具有讽刺意味的是让它看起来很急切!
更具体地说,interact
不是为实时用户输入而设计的——它是为文件输入而设计的,在这种情况下,您可以使用 bash 将文件通过管道传输到可执行文件中。它应该是 运行 像这样的东西:
$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt
如果你想要 line-by-line 缓冲,你可能必须手动完成,除非你想 'trick' 像 Willem 那样的编译器。下面是一些非常简单的代码,可以按您的预期工作——但请注意,它没有退出状态,这与 interact
不同,它将在 EOF 处终止。
main = do
ln <- getLine -- Buffers until you press enter
putStrLn ln -- Print the line we just got
main -- Loop forever
lines "a"
产生 ["a"]
的事实并不意味着如果您当前正在输入 a
,那么 lines
只是处理列表 [=12] 的输入=].您应该将输入视为(可能)无限的字符列表。如果提示正在等待用户输入,则下一次输入时 "blocking"。
但是 not 意味着像 lines
这样的函数可以 not 部分解析结果。 lines
以惰性方式实现,因此它处理字符流,每次看到换行符时,它都会开始发出下一个元素。因此,这意味着 lines
可以将无限的字符序列处理成无限的行列表。
但是,如果您使用 length :: Foldable f => f a -> Int
,则这需要评估列表(但不是列表的 元素 )。因此,这意味着 length
只会在 lines
开始发出下一个项目时发出答案。
您可以使用 seq
(和变体)在完成特定操作之前强制对术语求值。例如 seq :: a -> b -> b
会将第一个参数评估为 弱头部范式 (WHNF),然后 return 第二个参数。
在seq
的基础上,构建了其他功能,如seqList :: [a] -> [a]
in the Data.Lists
module of the lists
包。
我们可以使用它来推迟评估,直到第一行已知,例如:
-- will echo full lines
import Data.Lists(seqList)
interact (unlines . map (\x -> seqList x x) . lines)