Haskell:非严格和懒惰有何不同?

时间:2011-08-21 20:33:37

标签: haskell definition lazy-evaluation

6 个答案:

答案 0 :(得分:57)

非严格和懒惰,虽然可以非正式互换,但适用于不同的讨论领域。

非严格是指semantics:表达式的数学意义。非严格适用的世界没有功能,内存消耗甚至计算机的运行时间的概念。它简单地讨论了域映射中哪些类型的值到codomain中的哪种值。特别是, strict 函数必须将值⊥(“底部” - 请参阅上面的语义链接以获取更多相关信息)映射到⊥;非严格的功能是不允许这样做的。

Lazy 是指操作行为:代码在真实计算机上执行的方式。大多数程序员都会在程序上考虑程序,所以这可能就是你的想法。延迟评估是指使用thunks的实现 - 指向代码的指针,这些代码在第一次执行时被替换为值。注意这里的非语义词:“指针”,“第一次”,“已执行”。

懒惰的评估会产生非严格的语义,这就是为什么概念看起来如此接近。但正如FUZxxl指出的那样,懒惰并不是实现非严格语义的唯一方法。

如果您有兴趣了解有关此区别的更多信息,我强烈推荐上面的链接。阅读它是我对计算机程序含义概念的一个转折点。

答案 1 :(得分:16)

评估模型的一个示例,既不是严格也不是懒惰: 乐观评估,它提供了一些加速,因为它可以避免很多“容易”的thunk:

  

乐观评估意味着即使可能不需要子表达式来评估超表达式,我们仍然会使用一些启发式方法来评估其中一些表达式。如果子表达式没有足够快地终止,我们暂停其评估,直到真正需要它为止。如果稍后需要子表达式,这使得我们优于延迟评估,因为我们不需要生成thunk。另一方面,如果表达式没有终止,我们不会损失太多,因为我们可以很快地中止它。

正如您所看到的,此评估模型 not strict :如果评估产生_ | _的某些东西,但不需要,则该函数仍会终止,因为引擎会中止评估。另一方面,可能会评估比需要更多的表达式,因此它 完全 lazy

答案 2 :(得分:6)

是的,这里有一些术语使用不清楚,但在大多数情况下这些术语都是一致的,所以这不是太大的问题。

评估术语时的一个主要区别是。对此有多种策略,范围从“尽快”到“仅在最后一刻”。术语急切评估有时用于倾向于前者的策略,而懒惰评估恰当地指的是一系列倾向于后者的策略。 “懒惰评估”与相关策略之间的区别倾向于涉及评估某些事物的结果何时何地被保留,而不是抛在一边。在Haskell中熟悉的为数据结构分配名称并将其编入索引的memoization技术就是基于此。相比之下,一种简单地将表达式拼接在一起的语言(如“按姓名调用”评估)可能不支持这种语言。

另一个区别是评估哪些术语,范围从“绝对一切”到“尽可能少”。由于实际用于计算最终结果的任何值都不能忽略,因此这里的差异是评估了多少多余的术语。除了减少程序必须完成的工作量之外,忽略未使用的术语意味着它们不会产生任何错误。在绘制区别时,严格性指的是评估所考虑的所有内容的属性(例如,在严格函数的情况下,这意味着它应用的术语。它不会' t 必然意味着参数内的子表达式),而非严格意味着只评估一些事情(通过延迟评估或完全丢弃术语)。

应该很容易看出它们如何以复杂的方式相互作用;决策根本不是正交的,因为极端往往是不相容的。例如:

  • 非常严格的评估排除了一些急切的意愿;如果您不知道是否需要某个术语,则无法对其进行评估。

  • 非常严格的评价使得非热情有点无关紧要;如果您正在评估所有内容,那么 时这样做的决定就不那么重要了。

但确实存在替代定义。例如,至少在Haskell中,“严格函数”通常被定义为强制其参数足以使函数在任何参数的情况下评估为 | 的函数;请注意,根据此定义,id是严格的(在一个微不足道的意义上),因为强制id x的结果将与仅强制x具有完全相同的行为。

答案 3 :(得分:5)

这开始是一个更新,但它开始变长。

Laziness / Call-by-need是call-by-name的memoized版本,其中,如果评估函数参数,则存储该值以供后续使用。在“纯粹”(无效)设置中,这会产生与按名称调用相同的结果;当函数参数被使用两次或更多次时,按需调用几乎总是更快 势在必行的例子 - 显然这是可能的。有一篇关于Lazy Imperative Languages的有趣文章。它说有两种方法。一个需要闭包,第二个使用图缩减。由于C不支持闭包,因此需要将参数显式传递给迭代器。你可以包装一个地图结构,如果该值不存在,则计算它,否则返回值 注意:Haskell通过“指向代码的指针来实现这一点,这些代码在第一次执行时被替换为值” - luqui。
这是非严格的名称调用,但是对结果进行共享/记忆。

Call-By-Name - 在逐个调用的评估中,函数的参数在调用函数之前不会被计算 - 而是直接替换到函数体中(使用捕获避免替换)然后只要它们出现在函数中,就会被评估。如果函数体中没有使用参数,则永远不会计算参数;如果多次使用,每次出现时都会重新评估 势在必行示例:回调
注意:这是非严格的,因为如果不使用它会避免评估。

Non-Strict =在非严格评估中,除非它们实际用于评估函数体,否则不会评估函数的参数。
势在必行的例子:短路
注意:_ | _似乎是一种测试函数是否为非严格的方法

所以函数可以是非严格的但不是懒惰的。懒惰的函数总是非严格的。 按需拨打部分由按名称调用定义,部分由非严格

定义

摘自"Lazy Imperative Languages"

  

2.1。非严格的语义学懒惰评估我们必须首先澄清   “非严格语义”和“懒惰评价”之间的区别。   非严格语义是指定表达式不是   评估,直到原始操作需要它。可能有   各种类型的非严格语义。例如,非严格   过程调用donot评估参数,直到它们的值为止   需要。数据构造函数可能具有非严格语义,其中   复合数据是由未评估的作品组装而成的懒惰评价,   也称为延迟评估,是通常使用的技术   实现非严格的语义。在第4节中,通常有两种方法   用于实现惰性评估的内容非常简单。

     

按值收听,拨打拉兹,并按姓名拨打电话“按价值收费”是   用于具有严格语义的过程调用的通用名称。在电话中   通过valuelanguages,评估过程调用的每个参数   在程序调用之前;然后将值传递给   程序或附表达。按值调用的另一个名称是   “渴望”的评价。价值呼叫也被称为“应用秩序”   评估,因为在函数之前评估所有参数   应用于他们。“懒惰的呼唤”(使用威廉克林格的术语)   [8])是使用non-strict的过程调用的名称   语义。在通过惰性过程调用调用的语言中,   在被替换为过程之前,不会对参数进行评估   身体。懒惰评估的呼叫也被称为“正常   命令“评估,因为顺序(最里面到最里面,左边)   对表达式的评价。“名字叫”是一个   在Algol-60中使用的懒惰调用的特殊实现[18]。该   Algol-60的设计者打算使用按名称调用的参数   物理上被替换成程序体,括在括号内   并且在身体出现之前,使用适当的名称更改以避免冲突   评价。

     

打电话给拉齐VS.需要召唤按需拨打电话   懒惰的延伸呼叫,提示观察一个懒惰   通过记住给定的价值可以优化评估   延迟表达,一旦被迫,所以价值不需要   如果再次需要重新计算。通过需求评估,   因此,通过使用memoization来避免延迟调用   需要重复评估。弗里德曼和怀斯是其中之一   最早通过需求评估,提出“自杀倾向”   悬浮“在首次评估时自毁,   用自己的价值取代自己。

答案 4 :(得分:0)

按照我的理解,“非严格”意味着通过完成工作以较少的工作量来减少工作量。

“懒惰的评估”和类似的尝试通过避免完全完成(希望永远)来减少总体工作量。

从您的示例中...

f1() || f2()

...此表达式的短路可能不会导致触发“未来工作”,并且推理中没有任何投机/摊销因素,也没有任何计算复杂性的债务产生。

在C#示例中,“惰性”在整体视图中保留了一个函数调用,但是作为交换,它具有上述困难(至少从调用的角度直到可能完全完成...在此代码中)距离路径可以忽略不计,但可以想象这些功能具有一些高争用锁)。

int f1() { return 1;}
int f2() { return 2;}

int lazy(int (*cb1)(), int (*cb2)() , int x) {
    if (x == 0)
        return cb1();
    else
        return cb2();
}

int eager(int e1, int e2, int x) {
    if (x == 0)
         return e1;
    else
         return e2;
}

lazy(f1, f2, x);
eager(f1(), f2(), x);

答案 5 :(得分:-2)

如果我们谈论一般的计算机科学术语,那么“懒惰”和“非严格”通常是同义词 - 它们代表相同的整体观念,它以不同的方式表达不同的情况。

然而,在特定的特定背景下,他们可能已经获得了不同的技术含义。我不认为你可以说任何准确和普遍的关于“懒惰”和“非严格”之间的区别可能是在有不同之处的情况下。