在Haskell中列出惰性求值
为了理解Haskell中的列表惰性评估,我做了以下简单假设在Haskell中列出惰性求值,haskell,lazy-evaluation,Haskell,Lazy Evaluation,为了理解Haskell中的列表惰性评估,我做了以下简单假设 head [1, 2] -- expr1 head [1 .. 2] -- expr2 head [1 ..] -- expr3 head . (1 :) $ [] -- eval1 head . (1 :) . (2 :) $ [] -- eval2 我想expr3会像eval1那样被惰性地评估,那么expr1和expr2呢
head [1, 2] -- expr1
head [1 .. 2] -- expr2
head [1 ..] -- expr3
head . (1 :) $ [] -- eval1
head . (1 :) . (2 :) $ [] -- eval2
我想expr3
会像eval1
那样被惰性地评估,那么expr1
和expr2
呢
一般来说,
- Haskell中的延迟计算是编译时和运行时的一种技术吗
- 在时间、空间复杂度或程序逻辑上,哪里说效率高但难以推理
- 列表没有什么特别之处。它们只是递归数据类型:
data [a] = a : [a] | []
现在,当您使用[1..2]
时,即不直接变成一个列表(1:[])
!,它存储为表达式[1..2]
现在head
定义为:
head :: [a] -> a
head (x:_) = x
如果将head[1..2]
调用到main
(因此Haskell被迫对其求值),它将看到[1..2]
不是一个数据结构,而是一个未解析的表达式,它将稍微解析表达式:
[1 .. 2] to (1:[(succ 1) .. 2])
因此,现在我们读到:
head (1:[(succ 1) .. 2])
(注意尾部仍然是一个表达式),但由于head
只对“head”感兴趣,因此它将返回1
。注意,如果头
是例如1+2
,它也不会立即将其计算为3
此外,如果您只需调用head[1..2]
表达式将不会自动计算,例如,仅当您希望显示结果时,Haskell才会进行计算
根据编译器实现的不同,编译器可以在编译时传播常量(文字)并对其执行操作,但由于编译器应始终遵循执行标准,因此语义保持不变。术语“惰性评估”有多种用法
expr3
会像eval1
那样被惰性地评估,那么expr1
和expr2
呢
非严格的语义规定,对所有五个术语的求值都应终止并产生值1
,因此任何符合性的实现都将以这种方式进行。对于每个表达式,延迟计算将在大约相同的空间和时间内完成此操作。如果您强制执行这五个术语中的任何一个,我希望GHC会选择延迟求值,尽管经过优化后,GHC可能会在编译时执行求值。如果您非常感兴趣,可以通过传递-ddump siml
标志来检查这一点
Haskell中的惰性计算在编译时和运行时都是一种技术吗
希望上面的讨论已经澄清了这个问题。非严格语义描述编译时和运行时之间的特定连接(即,编译器必须生成一个程序,其运行时行为生成语义指定的值)。惰性评估是一种特殊的实现策略,用于生成符合语义的程序。GHC有时在其项目中使用惰性评估,但有时使用其他实施策略;但是,它符合非严格语义。(如果你找到一个没有的地方,那就是一个bug!)
在时间、空间复杂度或程序逻辑上,哪里说效率高但难以推理
非严格语义通常不会说明计算过程中使用了多少时间或空间,因此如果您想对此进行推理,则需要完全不同的技术。即使您决定将您的推理限制为使用惰性评估实现的程序,事情也可能会很困难。考虑像
[1..]
这样的表达式:这个表达式使用了多少空间?这个问题不能在真空中回答;惰性评估的基本思想是让值的消费者控制构建了多少值。因此,如果不了解程序如何处理表达式[1..]
,我们就无法了解太多。它可能会丢弃该值,在这种情况下几乎不使用任何空间;或者它可以沿着列表向下走,在这种情况下,会使用恒定的空间量;或者它可能在不同的时间遍历列表两次,在这种情况下使用无界空间;或者,它可能会对其他空间要求执行一百万其他操作。要完成其他答案,您可以使用ghci中的:sprint
命令检查惰性评估的工作方式:
Prelude> let xs = [1..10] :: [Int]
Prelude> :sprint xs
xs = _
Prelude> head xs
1
Prelude> :sprint xs
xs = 1 : _
Prelude> take 3 xs
[1,2,3]
Prelude> :sprint xs
xs = 1 : 2 : 3 : _
Prelude> length xs
10
Prelude> :sprint xs
xs = [1,2,3,4,5,6,7,8,9,10]
我不明白你在问什么。看起来您试图对表达式的求值进行推理,但我不知道您在示例中所说的是什么。在谷歌了解更多信息的关键词是“WHNF”。如果有人试图直接回答你的问题,那么答案将是:“在你的例子中,没有一个表达式被计算过”。你的意思是
head[1,2]
直接编译为1
吗?这个问题没有任何意义,因为在强制执行值之前(例如,您的代码使用该值执行IO),没有任何计算结果。什么main=print$head[1..]
将被编译二是使用的优化问题,它也可能被编译为“print 1”。此外,对诸如[a..b]
列表之类的内置结构进行推理也有点困难。如果你