具有头部功能的Haskell懒惰问题

时间:2018-10-10 04:58:06

标签: haskell

信息:

在“文档”和教程中,它说:默认情况下,“ 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还是惰性表达式?

谢谢

2 个答案:

答案 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。

正确,返回的“值”是所谓的“ thunk ”,它是尚未求值的表达式的另一个名称。什么意思是head a + head b

懒惰评估规则的主要例外是IO,它是急切评估的。因此,如果您想 print 调用f [2, 3] [4, 5]的结果,那么将对足够多的表达式求值以产生要打印的结果。

有必要的话,可以提前进行评估,例如通过使用seq。这有时可能很重要,因为这些重击可能会变大。