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

问题描述 投票:63回答:6

我经常读到懒惰与非严格不一样但我发现很难理解其中的区别。它们似乎可以互换使用,但我知道它们有不同的含义。我会很感激帮助理解差异。

关于这篇文章,我有几个问题。我将在本文末尾总结这些问题。我有几个示例片段,我没有测试它们,我只是将它们作为概念呈现。我添加了引号以避免查找它们。也许它会在以后有同样的问题帮助某人。

非严格的定义:

如果函数f应用于非终止表达式时,它也无法终止,则称函数f是严格的。换句话说,如果f bot的值为|,则f是严格的。对于大多数编程语言,所有功能都是严格的。但在Haskell中并非如此。作为一个简单的例子,考虑const1,常量1函数,定义如下:

const1 x = 1

Haskell中const1 bot的值为1.从操作上讲,由于const1不“需要”其参数的值,因此它永远不会尝试对其进行求值,因此永远不会陷入非终止计算中。出于这个原因,非严格函数也被称为“惰性函数”,据说可以“懒惰地”或“按需”来评估它们的参数。

-A Gentle Introduction To Haskell: Functions

我真的很喜欢这个定义。这似乎是我能找到的最好的理解严格的。 const1 x = 1也懒惰吗?

非严格意味着减少(评估的数学术语)从外部进行,

所以如果你有(a +(bc))那么首先减少+,然后你减少内部(bc)。

-Haskell Wiki: Lavy vs non-strict

Haskell Wiki真让我困惑。我理解他们对秩序的看法,但是我没有看到(a+(b*c))如果通过_|_非严格评估?

在非严格评估中,除非它们实际用于评估函数体,否则不评估函数的参数。

在教会编码下,运营商的懒惰评估映射到非严格的功能评估;因此,非严格评估通常被称为“懒惰”。许多语言中的布尔表达式使用一种称为短路评估的非严格评估形式,一旦可以确定将产生明确的布尔值,评估就会返回 - 例如,在遇到true的析取表达式中,或者遇到false的连接表达式,等等。条件表达式通常也使用惰性求值,一旦明确的分支将导致评估返回。

-Wikipedia: Evaluation Strategy

懒惰的Def:

另一方面,懒惰评估意味着仅在需要其结果时评估表达式(注意从“缩减”到“评估”的转变)。因此,当评估引擎看到一个表达式时,它会构建一个thunk数据结构,其中包含评估表达式所需的任何值,以及指向表达式本身的指针。当实际需要结果时,评估引擎调用表达式,然后将thunk替换为结果以供将来参考。 ...

显然,thunk和部分评估表达式之间存在强烈的对应关系。因此,在大多数情况下,术语“懒惰”和“非严格”是同义词。但并不完全。

-Haskell Wiki: Lavy vs non-strict

这似乎是Haskell的具体答案。我认为懒惰意味着thunks和非严格意味着部分评估。那个比较太简单了吗?懒惰总是意味着thunk和非严格总是意味着部分评估。

在编程语言理论中,惰性评估或按需调用1是一种评估策略,它延迟表达式的评估,直到其值实际需要(非严格评估)并且还避免重复评估(共享)。

-Wikipedia: Lazy Evaluation

势在必行的例子

我知道大多数人在学习函数式语言时会忘记命令式编程。但是,我想知道这些是否属于非严格,懒惰,两者兼而有之?至少它会提供熟悉的东西。

短路

f1() || f2()

C#,Python和其他语言“产量”

public static IEnumerable Power(int number, int exponent)
{
    int counter = 0;
    int result = 1;
    while (counter++ < exponent)
    {
        result = result * number;
        yield return result;
    }
}

-MSDN: yield (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);

问题

我知道答案就在我面前,拥有所有这些资源,但我无法理解。似乎这个定义很容易被忽视,因为暗示或显而易见。

我知道我有很多问题。随意回答您认为相关的任何问题。我添加了这些问题以供讨论。

  • const1 x = 1也很懒吗?
  • 如何从“内向”非严格评估?是因为向内允许减少不必要的表达,比如const1 x = 1?减少似乎符合懒惰的定义。
  • 懒惰总是意味着thunk和非严格总是意味着部分评估?这只是一个概括吗?
  • 以下命令性概念是懒惰,非严格,两者还是两者都没有? 短路 使用产量 传递回调以延迟或避免执行
  • 懒惰是非严格的子集,反之亦然,或者它们是互斥的。例如,可能是非严格而不是懒惰,或懒惰而不是非严格的?
  • Haskell的非严格性是否因懒惰而实现?

谢谢你这么!

haskell definition lazy-evaluation
6个回答
57
投票

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

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

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

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

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


16
投票

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

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

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


6
投票

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

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

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

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

  • 非严格的评估排除了一些热情;如果您不知道是否需要某个术语,则无法对其进行评估。
  • 非常严格的评估使得非热情有些无关紧要;如果您正在评估所有内容,那么何时这样做的决定就不那么重要了。

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


5
投票

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

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

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

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

所以函数可以是非严格的但不是懒惰的。懒惰的函数总是非严格的。 Call-By-Need部分由Call-By-Name定义,其部分由Non-Strict定义

来自"Lazy Imperative Languages"的摘录

2.1。非严格的语义学懒惰评估我们必须首先澄清“非严格语义”和“懒惰评估”之间的区别。非严格语义是指定表达式在初始操作需要之前不进行求值的语义。可能存在各种类型的非严格语义。例如,非严格的过程调用不会评估参数,直到需要它们的值。数据构造函数可能具有非严格语义,其中复合数据由未评估的部分组合。惰性评估(也称为延迟评估)是通常用于实现非严格语义的技术。在第4节中,通常用于实现延迟评估的两种方法非常简单地概括。

按值调用,通过LAZY调用和按名称调用“按值调用”是用于具有严格语义的过程调用的通用名称。在valuelanguages调用中,在进行过程调用之前评估过程调用的每个参数;然后将值传递给过程或封闭表达式。按值调用的另一个名称是“急切”评估。按值调用也称为“应用顺序”评估,因为所有参数在函数应用之前都会被评估。“懒惰调用”(使用William Clinger的术语[8] ])是使用非严格语义的过程调用的名称。在通过惰性过程调用调用的语言中,在被替换到过程体之前不会对参数进行求值。懒惰评估的调用也称为“正常顺序”评估,因为表达式的评估顺序(从最里面到最里面,从左到右)。“按名称调用”是懒惰调用的特定实现,在Algol中使用-60 [18]。 Algol-60的设计者希望将名称调用参数物理地替换到过程体中,括号括起来并使用适当的名称更改以避免冲突,然后再评估主体。

打电话给拉齐VS.按需调用按需调用是延迟调用的延伸,由观察提示,一旦强制,通过记住给定延迟表达式的值可以优化惰性求值,以便在需要时再次计算该值不需要重新计算。因此,通过需求评估调用,通过使用记忆来扩展调用惰性方法,以避免重复评估的需要。弗里德曼和怀斯是最早通过需求评估的呼吁之一,提出了“自杀性悬浮”,当他们第一次被评估时自我毁灭,用自己的价值取代自己。


0
投票

我理解它的方式,“非严格”意味着试图通过在较少量的工作中完成来减少工作量。

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

从你的例子......

f1() || f2()

......从这个表达式中短路不会导致触发“未来的工作”,并且既没有推理/摊销因素也没有推算,也没有任何计算复杂性的债务累积。

而在C#示例中,“lazy”在整个视图中保留了一个函数调用,但作为交换,它带来了上述各种难度(至少从调用点到可能完全完成...在此代码中可以忽略不计 - 距离路径,但想象一下这些功能有一些高争用的锁定来忍受)。

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);

-2
投票

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

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

© www.soinside.com 2019 - 2024. All rights reserved.