信息:
在“文档”和教程中,它说:默认情况下,“ Haskell是懒惰的”。
他们不解释细节,我想知道。
现在我知道如果我写:
filter odd [1, 2, 3]
在结果显示或在表达式中使用结果之前,它不会过滤结果。
对此我有几个问题:
head
函数是否很懒?
如果不是,为什么不偷懒?
如果很懒,Haskell编译器如何知道何时执行该功能?
我举一个例子:
f a b = head a + head b
f [2, 3] [4, 5]
在这种情况下,我的脑袋会不返回2 + 4。
它将返回某些类型或函数,稍后将在需要时执行。 (如果我在某个地方弄错了,请纠正我)。
所以我的建议是,当Haskell看到类似“ +”的操作时,它将计算结果。
但是它变得更加复杂,因为对于整数,我想如果我写3 + 5
,它也可以是惰性表达式。
我怀疑在懒惰的表达式开始计算时是否存在带有函数的列表,因为很难编写所有变体。
最后:
f head [1, 2]
让我们在f
函数中说,我打印了传递的变量的类型。
现在Haskell将如何知道应该传递Int 1还是惰性表达式?
谢谢
答案 0 :(得分:4)
我认为这里有些混乱,因为有时在两个不同的上下文中使用“懒惰”一词。
关于懒惰与渴望的语义:请考虑以下表达式
(\x -> 42) (error "urk!")
对以上内容进行评估时,结果是什么?
根据渴望语义,我们在调用函数之前先评估参数。结果将是运行时错误。
根据惰性语义,我们立即调用该函数。此过程可以在操作上和符号上理解如下。
操作上,它传递了一个 thunk ,该对象描述了尚未评估的参数,并且在需要参数x
时将被“强制”(评估) 。我们可以假设x
指向未求值的表达式error "urk!"
,该表达式将在需要x
时求值。
从符号上讲,我们使用数学技巧:我们使用称为“底部”的特殊值来表示错误,并说error "urk!"
具有这种最低值。
然后,我们简单地假装可以传递这个非凡的价值。在上面的函数调用中,x
将绑定到“底部”,就好像它是正常值一样。可以说这很简单,因为我们不需要关注表达式的求值方式,而只需关注底部的传播方式。
更准确地说,我们让“底部”代表运行时错误和非终止(无限递归),这两者都使程序摆脱了返回实际结果的麻烦。
例如,我们有if bottom then .. else ..
将总是产生底部。与bottom + 4
类似,位于底部。同样,case bottom of SomeConstructor -> ...; ...
位于底部(很好,newtypes
除外,但是我们忽略它)。取而代之的是,f bottom
可能根据f
的使用情况而定为底部:如果需要自变量,则结果将为底部。
关于惰性(非严格)功能。如果f
是底部的,则有时称函数f bottom
是“懒惰的”(或更恰当地说,是非严格的)。
例如:
f x = x+1 -- strict / non lazy
f x = 5 -- non strict / lazy
head xs = case xs of -- strict / non lazy
[] -> error "head: empty list"
(x:xs) -> x
g x = (x,True) -- non strict / lazy
因此,由于head bottom
是底部的case bottom of ...
,因此head
并不懒惰。从操作上讲,由于head
在产生结果之前需要其参数,因此它是严格/非惰性的。
关于g
:惰性语义的主要特征是像对构造函数data
这样的(,)
构造函数本质上是惰性的。也就是说(bottom, 4)
与底部不同:即使第一对组件是“错误”值,也可以拥有snd (bottom, 4) = 4
。
因此,g bottom = (bottom, True)
并非最底端,我们可以应用snd
提取True
而不会触发错误。
答案 1 :(得分:2)
head
函数是否很懒?
是的,Haskell默认情况下是惰性计算。
如果很懒,Haskell编译器如何知道何时执行该功能?
该函数将在需要该值时进行评估-据我了解,最终会在您以某种方式涉及IO
时发生。
在这种情况下,从我的角度来看,头部将不会返回2 + 4。
正确,返回的“值”是所谓的“ head a + head b
。
懒惰评估规则的主要例外是IO
,它是急切评估的。因此,如果您想 print 调用f [2, 3] [4, 5]
的结果,那么将对足够多的表达式求值以产生要打印的结果。
有必要的话,可以提前进行评估,例如通过使用seq
。这有时可能很重要,因为这些重击可能会变大。