了解Haskell的懒惰

时间:2019-06-24 08:51:20

标签: haskell lazy-evaluation

我正在阅读:Haskell interact function

所以我尝试了

interact (unlines . map (show . length) . lines)

它按我的预期工作。我输入一些内容,按Enter键,然后在提示处打印出长度。

所以我想尝试使其简单地重复我键入的内容,所以我尝试了

interact (unlines . map id . lines)

但是现在它会重复我键入的每个字符。为什么呢?我以为诀窍在lines之后,接着是unlines-但事实并非如此。 lines "a"会产生["a"],所以当我开始输入输入时,第一个函数又如何呢?它不只是立即给出“ 1”作为输出?显然,我对“发现字符串的长度不是这样-在生成任何输出之前必须知道整个字符串”感到误解。

2 个答案:

答案 0 :(得分:3)

这与延迟评估有关。我将尝试以尽可能直观的方式对此进行解释。

在编写interact (unlines . map (show . length) . lines)时,每次输入一个字符时,我们实际上都不知道下一个输出字符是什么,除非您按Enter键。因此,您将获得预期的行为。

但是,在interact (unlines . map id . lines) = interact id中的每个点上,每次输入一个字符时,都可以确保该字符包含在输出中。因此,如果您输入一个字符,该字符也会立即输出。

这是“懒惰”一词用词不当的原因之一。诚然,Haskell只会在需要时评估某些东西,但是另一方面,当需要时,它会尽快进行评估。 Haskell在这里需要对输出进行评估,因为您要打印它,因此它会尽可能地对其进行评估-一次一个字符-具有讽刺意味的是,它看起来似乎很渴望!

更具体地说,interact不是用于实时用户输入,而是用于文件输入,在文件输入中,您使用bash将文件通过管道传输到可执行文件中。应该运行这样的东西:

$ runhaskell Interactor.hs < my_big_file.txt > list_of_lengths.txt

如果要逐行缓冲,则可能必须手动执行,除非您想像Willem一样“欺骗”编译器。这是一些非常简单的代码,可以按您期望的那样工作,但是请注意,它没有退出状态,与interact不同,后者会终止在EOF。

main = do
  ln <- getLine -- Buffers until you press enter
  putStrLn ln   -- Print the line we just got
  main          -- Loop forever

答案 1 :(得分:3)

lines "a"产生["a"]的事实并不意味着如果您当前正在输入a,那么lines只会处理列表["a"]的输入。 。您应该将输入视为(可能)无限的字符列表。如果提示正在等待用户输入,则提示将“阻塞”下一次输入。

但是 not 并不意味着像lines这样的函数可以 not 已经部分解析了结果。 lines以一种惰性方式实现,以便处理字符流,并且每次看到换行符时,它就会开始发出下一个元素。因此,这意味着lines可以将无限个字符序列处理成无限的行列表。

但是,如果您使用length :: Foldable f => f a -> Int,则需要对列表进行评估(但是,不是列表的 elements )。因此,这意味着length只会从lines开始发出下一个项目的那一刻起给出答案。

您可以使用seq(和变体)在执行某些操作之前强制对术语进行评估。例如,seq :: a -> b -> b会将第一个参数求值为弱头范式(WHNF),然后返回第二个参数。

基于seq,还构建了其他功能,例如seqList :: [a] -> [a]包的Data.Lists module中的lists

我们可以使用它来推迟评估,直到知道第一行为止,例如:

-- will echo full lines

import Data.Lists(seqList)

interact (unlines . map (\x -> seqList x x) . lines)