关于定义:Haskell:非严格和懒惰有何不同?

Haskell: How does non-strict and lazy differ?

我经常读到懒惰和不严格是不一样的,但我发现很难理解两者的区别。它们似乎可以互换使用,但我理解它们有不同的含义。我希望能得到一些帮助来理解这种差异。

我有一些关于这篇文章的问题。我将在这篇文章的最后总结这些问题。我有几个示例片段,我没有测试它们,我只是将它们作为概念呈现。我添加了一些引用,以避免您查找它们。也许它能在以后的问题上帮助别人。

非严格DEF:

A function f is said to be strict if, when applied to a nonterminating
expression, it also fails to terminate. In other words, f is strict
iff the value of f bot is |. For most programming languages, all
functions are strict. But this is not so in Haskell. As a simple
example, consider const1, the constant 1 function, defined by:

const1 x = 1

The value of const1 bot in Haskell is 1. Operationally speaking, since
const1 does not"need" the value of its argument, it never attempts to
evaluate it, and thus never gets caught in a nonterminating
computation. For this reason, non-strict functions are also called
"lazy functions", and are said to evaluate their arguments"lazily",
or"by need".

-简单介绍haskell:功能

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

Non-strictness means that reduction (the mathematical term for
evaluation) proceeds from the outside in,

so if you have (a+(bc)) then first you reduce the +, then you reduce
the inner (bc).

-haskell wiki:lavy vs non-strict

哈斯克尔维基真的让我困惑。我理解他们对订单的看法,但我不明白如果订单通过,(a+(b*c))将如何进行非严格的评估?

In non-strict evaluation, arguments to a function are not evaluated
unless they are actually used in the evaluation of the function body.

Under Church encoding, lazy evaluation of operators maps to non-strict
evaluation of functions; for this reason, non-strict evaluation is
often referred to as"lazy". Boolean expressions in many languages use
a form of non-strict evaluation called short-circuit evaluation, where
evaluation returns as soon as it can be determined that an unambiguous
Boolean will result — for example, in a disjunctive expression where
true is encountered, or in a conjunctive expression where false is
encountered, and so forth. Conditional expressions also usually use
lazy evaluation, where evaluation returns as soon as an unambiguous
branch will result.

-维基百科:评估策略

懒惰:

Lazy evaluation, on the other hand, means only evaluating an
expression when its results are needed (note the shift from
"reduction" to"evaluation"). So when the evaluation engine sees an
expression it builds a thunk data structure containing whatever values
are needed to evaluate the expression, plus a pointer to the
expression itself. When the result is actually needed the evaluation
engine calls the expression and then replaces the thunk with the
result for future reference.
...

Obviously there is a strong correspondence between a thunk and a
partly-evaluated expression. Hence in most cases the terms"lazy" and
"non-strict" are synonyms. But not quite.

-haskell wiki:lavy vs non-strict

这似乎是哈斯克尔特定的答案。我认为懒惰意味着雷鸣,不严格意味着部分评价。这个比较太简单了吗?懒惰总是意味着雷鸣,不严格总是意味着局部评价。

In programming language theory, lazy evaluation or call-by-need1 is
an evaluation strategy which delays the evaluation of an expression
until its value is actually required (non-strict evaluation) and also
avoid repeated evaluations (sharing).

-维基百科:懒惰的评价

强制性示例

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

短路

1
f1() || f2()

C、python和其他语言的"yield"

1
2
3
4
5
6
7
8
9
10
public static IEnumerable Power(int number, int exponent)
{
    int counter = 0;
    int result = 1;
    while (counter++ < exponent)
    {
        result = result * number;
        yield return result;
    }
}

-msdn:产量(c)

回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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中那样?减少似乎符合懒惰的定义。
  • 懒惰总是意味着雷鸣,不严格总是意味着局部评价?这只是一个概括吗?
  • 以下命令式概念是懒惰的、非严格的、两者都有还是两者都没有?
    • 短路
    • 利用产量
    • 将回调传递给延迟或避免执行
  • 懒惰是非严格的一个子集,反之亦然,或者它们相互排斥。例如,有没有可能不严格不懒惰,或懒惰不严格?
  • 哈斯克尔的不严格是由懒惰实现的吗?

谢谢!


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

非严格是指语义:一个表达式的数学意义。非严格应用的世界不存在函数运行时间、内存消耗甚至计算机的概念。它只是简单地讨论域中哪些类型的值映射到codomain中哪些类型的值。特别是,严格函数必须将值&perp;("bottom"--请参阅上面的语义链接了解更多信息)映射到&perp;;不允许非严格函数执行此操作。

懒惰是指操作行为:代码在实际计算机上执行的方式。大多数程序员都是从操作上考虑程序的,所以这可能就是您所想的。懒惰的评估是指使用thunk的实现——指向代码的指针,这些代码在第一次执行时被替换为一个值。注意这里的非语义词:"指针"、"第一次"、"已执行"。

懒惰的评估产生了不严格的语义,这就是为什么概念看起来如此紧密。但是正如fuzzyl指出的,惰性并不是实现非严格语义的唯一途径。

如果您有兴趣进一步了解这一区别,我强烈推荐上面的链接。阅读它是我对计算机程序意义的一个转折点。


一个既不严格也不懒惰的评估模型的例子是:乐观的评估,因为它可以避免很多"简单"的雷鸣声,所以可以加快速度:

Optimistic evaluation means that even if a subexpression may not be needed to evaluate the superexpression, we still evaluate some of it using some heuristics. If the subexpression doesn't terminate quickly enough, we suspend its evaluation until it's really needed. This gives us an advantage over lazy evaluation if the subexpression is needed later, as we don't need to generate a thunk. On the other hand, we don't lose too much if the expression doesn't terminate, as we can abort it quickly enough.

如您所见,此评估模型并不严格:如果对生成的内容进行了评估,但不需要进行评估,则当引擎中止评估时,函数仍将终止。另一方面,计算的表达式可能比需要的要多,所以它不是完全懒惰的。


是的,这里有些术语的用法不明确,但无论如何,大多数情况下术语都是一致的,所以这不是一个太大的问题。

一个主要的区别是对术语进行评估。为此,有多种策略,从"尽快"到"只在最后一刻"不等。"渴望评估"一词有时用于倾向于前者的策略,而"懒惰评估"一词恰当地指的是倾向于后者的一系列策略。"懒惰的评估"和相关策略之间的区别往往涉及评估结果何时何地被保留,而不是被丢弃。Haskell中常见的将名称分配给数据结构并对其进行索引的记忆化技术就是基于此。相反,简单地将表达式拼接到彼此中(如"按名称调用"计算中)的语言可能不支持这种情况。

另一个区别是对哪些术语进行评估,从"绝对所有"到"尽可能少"。由于任何实际用于计算最终结果的值都不能被忽略,所以这里的区别在于计算了多少多余的项。除了减少程序必须做的工作量之外,忽略未使用的术语意味着它们将不会产生任何错误。当一个区别被画出来时,严格性是指评估所有正在考虑的事物的属性(例如,在一个严格的函数的情况下,这意味着它被应用到的术语)。它不一定意味着参数中的子表达式),而非严格意味着只计算某些东西(要么延迟计算,要么完全丢弃术语)。

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

    百万千克1

    非常不严格的评估排除了一些渴望;如果你不知道是否需要一个术语,你还不能评估它。

    百万千克1百万千克1

    非常严格的评估会让非渴望变得有些无关紧要;如果你在评估每件事,那么什么时候这样做的决定就不那么重要了。

    百万千克1

不过,替代定义确实存在。例如,至少在haskell中,"严格函数"通常被定义为一个充分强制其参数的函数,当任何参数进行计算时,该函数都将计算为;请注意,根据这个定义,id是严格的(在很小的意义上),因为强制id x的结果与强制具有完全相同的行为。仅限于x


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

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

按名称调用-在按名称调用计算中,在调用函数之前不会计算函数的参数-相反,它们直接替换到函数体中(使用捕获避免替换),然后在函数中出现时留下来进行计算。如果参数未在函数体中使用,则从不计算该参数;如果多次使用该参数,则每次出现时都会重新计算该参数。
命令式示例:回调
注意:这是不严格的,因为如果不使用它可以避免评估。

non-strict=在非严格计算中,除非函数的参数实际用于函数体的计算,否则不计算函数的参数。
强制性示例:短路
注意:似乎是一种测试函数是否非严格的方法

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

摘自"懒惰的命令式语言"

2.1. NON-STRICT SEMANTICS VS. LAZY EVALUATION We must first clarify
the distinction between"non-strict semantics" and"lazy evaluation".
Non-strictsemantics are those which specify that an expression is not
evaluated until it is needed by a primitiveoperation. There may be
various types of non-strict semantics. For instance, non-strict
procedure calls donot evaluate the arguments until their values are
required. Data constructors may have non-strictsemantics, in which
compound data are assembled out of unevaluated pieces Lazy evaluation,
also called delayed evaluation, is the technique normally used to
implement non-strictsemantics. In section 4, the two methods commonly
used to implement lazy evaluation are very brieflysummarized.

CALL BY VALUE, CALL BY LAZY, AND CALL BY NAME"Call by value" is the
general name used for procedure calls with strict semantics. In call
by valuelanguages, each argument to a procedure call is evaluated
before the procedure call is made; the value isthen passed to the
procedure or enclosing expression. Another name for call by value is
"eager" evaluation.Call by value is also known as"applicative order"
evaluation, because all arguments are evaluated beforethe function is
applied to them."Call by lazy" (using William Clinger's terminology in
[8]) is the name given to procedure calls which usenon-strict
semantics. In languages with call by lazy procedure calls, the
arguments are not evaluatedbefore being substituted into the procedure
body. Call by lazy evaluation is also known as"normal
order"evaluation, because of the order (outermost to innermost, left
to right) of evaluation of an expression."Call by name" is a
particular implementation of call by lazy, used in Algol-60 [18]. The
designers ofAlgol-60 intended that call-by-name parameters be
physically substituted into the procedure body, enclosedby parentheses
and with suitable name changes to avoid conflicts, before the body was
evaluated.

CALL BY LAZY VS. CALL BY NEED Call by need is an
extension of call by lazy, prompted by the observation that a lazy
evaluation could beoptimized by remembering the value of a given
delayed expression, once forced, so that the value need notbe
recalculated if it is needed again. Call by need evaluation,
therefore, extends call by lazy methods byusing memoization to avoid
the need for repeated evaluation. Friedman and Wise were among the
earliestadvocates of call by need evaluation, proposing"suicidal
suspensions" which self-destructed when theywere first evaluated,
replacing themselves with their values.


在我理解它的方式中,"不严格"意味着通过以较低的工作量完成工作来减少工作量。

而"懒惰的评估"和类似的方法则试图通过避免完全完成来减少总体工作量(希望永远如此)。

从你的例子来看…

1
f1() || f2()

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

而在C示例中,"lazy"在整体视图中保存了一个函数调用,但作为交换,它也遇到了上述各种困难(至少从调用点到可能的完全完成……在这段代码中,这是一条可以忽略的距离路径,但是假设这些函数有一些高争用锁需要忍受)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);

如果我们说的是一般的计算机科学术语,那么"懒惰"和"不严格"通常是同义词——它们代表着相同的总体概念,在不同的情况下,它们以不同的方式表达自己。

但是,在特定的专门上下文中,它们可能具有不同的技术含义。我不认为你能对"懒惰"和"不严格"之间的区别说任何准确和普遍的话,在这种情况下,有什么区别是可能的。