非严格和惰性有何不同?
我经常读到惰性与非严格不同,但我发现很难理解其中的区别。它们似乎可以互换使用,但我知道它们有不同的含义。我希望能得到一些帮助来理解其中的差异。
我有几个关于这篇文章的问题。我将在本文末尾总结这些问题。我有一些示例片段,我没有测试它们,我只是将它们作为概念呈现。我添加了引号,以免您查找它们。也许它会对以后遇到同样问题的人有所帮助。
非严格定义:
函数 f 被称为严格函数,如果应用于非终止函数 表达式,它也无法终止。换句话说,f 是严格的 当且仅当 f bot 的值为 |。对于大多数编程语言来说,所有 功能是严格的。但在 Haskell 中却并非如此。作为一个简单的 例如,考虑 const1,常量 1 函数,定义为:
const1 x = 1
Haskell 中 const1 bot 的值为 1。从操作上来说,因为 const1 并不“需要”其参数的值,它从不尝试 评估它,因此永远不会陷入无休止的困境 计算。因此,非严格函数也称为 “惰性函数”,据说会“惰性地”评估它们的参数, 或“根据需要”。
我真的很喜欢这个定义。这似乎是我能找到的最好的理解严格的方法。 const1 x = 1
也是懒惰的吗?
非严格性意味着减少(数学术语 评估)从外到内的收益,
所以如果你有 (a+(bc)) 那么首先你减少 +,然后你减少 内部 (bc)。
Haskell Wiki 真的让我很困惑。我理解他们所说的关于顺序的内容,但我不明白如果通过 _|_
,(a+(b*c))
将如何非严格地评估?
在非严格求值中,不求值函数的参数 除非它们实际用于函数体的求值。
在 Church 编码下,运算符的惰性求值映射到非严格 功能评估;因此,非严格评估是 通常被称为“懒惰”。许多语言都使用布尔表达式 一种称为短路评估的非严格评估形式,其中 一旦可以确定明确的评估返回 将产生布尔值 - 例如,在析取表达式中,其中 遇到 true 或在连接表达式中遇到 false 时 遇到过等等。条件表达式也通常使用 惰性求值,一旦得到明确的结果,求值立即返回 将产生分支。
惰性定义:
另一方面,惰性评估意味着仅评估 需要其结果时的表达式(注意从 “减少”为“评估”)。因此,当评估引擎看到 表达式它构建一个包含任何值的 thunk 数据结构 需要计算表达式,加上一个指向 表达本身。当实际需要结果时进行评估 引擎调用表达式,然后用 thunk 替换 结果供将来参考。 ...
显然 thunk 和 a 之间有很强的对应关系 部分评估的表达式。因此,在大多数情况下,术语“惰性”和 “非严格”是同义词。但也不完全是。
这似乎是 Haskell 的具体答案。我认为惰性意味着thunk,非严格意味着部分评估。这样的比较是不是太简单化了? lazy 是否总是意味着 thunk,而 non-strict 总是意味着部分评估。
在编程语言理论中,惰性求值或按需调用1 是 延迟表达式求值的求值策略 直到实际需要它的值(非严格评估)并且 避免重复评估(共享)。
命令式示例
我知道大多数人都说在学习函数式语言时忘记命令式编程。但是,我想知道这些是否符合非严格、惰性、两者或两者都没有的资格?至少它会提供一些熟悉的东西。
短路
f1() || f2()
C#、Python 和其他有“yield”的语言
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
回调
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
也是懒惰的吗?- 从“内部”评估如何是非严格的?是因为 inward 允许减少不必要的表达式,比如 const1 x = 1 吗?减少似乎符合懒惰的定义。
- 惰性是否总是意味着thunks,而非严格总是意味着部分评估?这只是一个概括吗?
- 以下命令式概念是惰性的、非严格的、两者都是还是两者都不是?
- 短路
- 使用产量
- 传递回调以延迟或避免执行
- 惰性是非严格的子集,反之亦然,或者它们是互斥的。例如,是否可以做到非严格而不懒惰,或者懒惰而不非严格?
- Haskell 的不严格是通过懒惰实现的吗?
谢谢你!
I often read that lazy is not the same as non-strict but I find it hard to understand the difference. They seem to be used interchangeably but I understand that they have different meanings. I would appreciate some help understanding the difference.
I have a few questions which are scattered about this post. I will summarize those questions at the end of this post. I have a few example snippets, I did not test them, I only presented them as concepts. I have added quotes to save you from looking them up. Maybe it will help someone later on with the same question.
Non-Strict 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".
-A Gentle Introduction To Haskell: Functions
I really like this definition. It seems the best one I could find for understanding strict. Is const1 x = 1
lazy as well?
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: Lazy vs non-strict
The Haskell Wiki really confuses me. I understand what they are saying about order but I fail to see how (a+(b*c))
would evaluate non-strictly if it was pass _|_
?
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.
-Wikipedia: Evaluation Strategy
Lazy Def:
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: Lazy vs non-strict
This seems like a Haskell specific answer. I take that lazy means thunks and non-strict means partial evaluation. Is that comparison too simplified? Does lazy always mean thunks and non-strict always mean partial evaluation.
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).
Imperative Examples
I know most people say forget imperative programming when learning a functional language. However, I would like to know if these qualify as non-strict, lazy, both or neither? At the very least it would provide something familiar.
Short Circuiting
f1() || f2()
C#, Python and other languages with "yield"
public static IEnumerable Power(int number, int exponent)
{
int counter = 0;
int result = 1;
while (counter++ < exponent)
{
result = result * number;
yield return result;
}
}
Callbacks
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);
Questions
I know the answer is right in front of me with all those resources, but I can't grasp it. It all seems like the definition is too easily dismissed as implied or obvious.
I know I have a lot of questions. Feel free to answer whatever questions you feel are relevant. I added those questions for discussion.
- Is
const1 x = 1
also lazy? - How is evaluating from "inward" non-strict? Is it because inward allows reductions of unnecessary expressions, like in
const1 x = 1
? Reductions seem to fit the definition of lazy. - Does lazy always mean thunks and non-strict always mean partial evaluation? Is this just a generalization?
- Are the following imperative concepts Lazy, Non-Strict, Both or Neither?
- Short Circuiting
- Using yield
- Passing Callbacks to delay or avoid execution
- Is lazy a subset of non-strict or vice versa, or are they mutually exclusive. For example is it possible to be non-strict without being lazy, or lazy without being non-strict?
- Is Haskell's non-strictness achieved by laziness?
Thank you SO!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
非严格和惰性虽然非正式地可以互换,但适用于不同的讨论领域。
非严格指语义:表达式的数学含义。非严格适用的世界没有函数的运行时间、内存消耗甚至计算机的概念。它只是讨论域中的哪些类型的值映射到共域中的哪些类型的值。特别是,严格函数必须将值 ⊥(“bottom”——有关更多信息,请参阅上面的语义链接)映射到 ⊥;允许非严格函数不这样做。
惰性指的是操作行为:代码在真实计算机上执行的方式。大多数程序员都从操作角度考虑程序,所以这可能就是您的想法。惰性求值是指使用 thunk 的实现——指向代码的指针,这些代码在第一次执行时被值替换。注意这里的非语义词:“指针”、“第一次”、“执行”。
惰性求值会产生不严格的语义,这就是为什么这些概念看起来如此接近。但正如 FUZxxl 指出的那样,惰性并不是实现非严格语义的唯一方法。
如果您有兴趣了解有关此区别的更多信息,我强烈推荐上面的链接。阅读这本书是我对计算机程序含义概念的转折点。
Non-strict and lazy, while informally interchangeable, apply to different domains of discussion.
Non-strict refers to semantics: the mathematical meaning of an expression. The world to which non-strict applies has no concept of the running time of a function, memory consumption, or even a computer. It simply talks about what kinds of values in the domain map to which kinds of values in the codomain. In particular, a strict function must map the value ⊥ ("bottom" -- see the semantics link above for more about this) to ⊥; a non strict function is allowed not to do this.
Lazy refers to operational behavior: the way code is executed on a real computer. Most programmers think of programs operationally, so this is probably what you are thinking. Lazy evaluation refers to implementation using thunks -- pointers to code which are replaced with a value the first time they are executed. Notice the non-semantic words here: "pointer", "first time", "executed".
Lazy evaluation gives rise to non-strict semantics, which is why the concepts seem so close together. But as FUZxxl points out, laziness is not the only way to implement non-strict semantics.
If you are interested in learning more about this distinction, I highly recommend the link above. Reading it was a turning point in my conception of the meaning of computer programs.
评估模型的一个示例,既不是严格也不是惰性:乐观评估,它提供了一些加速,因为它可以避免很多“简单”谢谢:
正如您所看到的,此评估模型并不严格:如果对产生 _|_ 的内容进行了评估,但不需要,则该函数仍将终止,因为引擎会中止评估。另一方面,可能会计算超出所需数量的表达式,因此它不完全惰性。
An example for an evaluation model, that is neither strict nor lazy: optimistic evaluation, which gives some speedup as it can avoid a lot of "easy" thunks:
As you can see, this evaluation model is not strict: If something that yields _|_ is evaluated, but not needed, the function will still terminate, as the engine aborts the evaluation. On the other hand, it may be possible that more expressions than needed are evaluated, so it's not completely lazy.
是的,这里有一些术语的使用不清楚,但无论如何,这些术语在大多数情况下都是一致的,所以这不是什么太大的问题。
一个主要区别在于评估条款的时间。对此有多种策略,范围从“尽快”到“仅在最后一刻”。术语急切评估有时用于表示倾向于前者的策略,而惰性评估则正确地指代一系列严重倾向于后者的策略。 “惰性评估”和相关策略之间的区别往往涉及评估某物的结果何时何地被保留,而不是被扔到一边。 Haskell 中为数据结构分配名称并为其建立索引的熟悉的记忆技术就是基于此。相反,简单地将表达式相互拼接的语言(如“按名称调用”计算)可能不支持这一点。
另一个区别是评估哪些术语,范围从“绝对所有”到“尽可能少”。由于实际用于计算最终结果的任何值都不能被忽略,因此这里的区别在于评估了多少多余的项。除了减少程序必须完成的工作量之外,忽略未使用的术语还意味着它们不会产生任何错误。当进行区分时,严格性指的是评估所考虑的所有事物的属性(例如,在严格函数的情况下,这意味着它所应用的术语。它确实如此) t 必然意味着参数内的子表达式),而非严格意味着仅评估某些事物(通过延迟评估或完全丢弃术语)。
应该很容易看出它们如何以复杂的方式相互作用;决策根本不是正交的,因为极端情况往往是不相容的。例如:
非常不严格的评估会排除一定程度的渴望;如果您不知道某个术语是否需要,您还无法评估它。
非常严格的评估使得不热心有些无关紧要;如果你正在评估一切,那么何时这样做的决定就不那么重要了。
不过,确实存在替代定义。例如,至少在 Haskell 中,“严格函数”通常被定义为一个足以强制其参数的函数,无论何时任何参数出现,该函数都会计算为 _|_ (“底部”);请注意,根据此定义,
id
是严格的(在微不足道的意义上),因为强制id x
的结果将与强制x< 具有完全相同的行为/code> 单独。
Yes, there is some unclear use of terminology here, but the terms coincide in most cases regardless, so it's not too much of a problem.
One major difference is when terms are evaluated. There are multiple strategies for this, ranging on a spectrum from "as soon as possible" to "only at the last moment". The term eager evaluation is sometimes used for strategies leaning toward the former, while lazy evaluation properly refers to a family of strategies leaning heavily toward the latter. The distinction between "lazy evaluation" and related strategies tend to involve when and where the result of evaluating something is retained, vs. being tossed aside. The familiar memoization technique in Haskell of assigning a name to a data structure and indexing into it is based on this. In contrast, a language that simply spliced expressions into each other (as in "call-by-name" evaluation) might not support this.
The other difference is which terms are evaluated, ranging from "absolutely everything" to "as little as possible". Since any value actually used to compute the final result can't be ignored, the difference here is how many superfluous terms are evaluated. As well as reducing the amount of work the program has to do, ignoring unused terms means that any errors they would have generated won't occur. When a distinction is being drawn, strictness refers to the property of evaluating everything under consideration (in the case of a strict function, for instance, this means the terms it's applied to. It doesn't necessarily mean sub-expressions inside the arguments), while non-strict means evaluating only some things (either by delaying evaluation, or by discarding terms entirely).
It should be easy to see how these interact in complicated ways; decisions are not at all orthogonal, as the extremes tend to be incompatible. For instance:
Very non-strict evaluation precludes some amount of eagerness; if you don't know whether a term will be needed, you can't evaluate it yet.
Very strict evaluation makes non-eagerness somewhat irrelevant; if you're evaluating everything, the decision of when to do so is less significant.
Alternate definitions do exist, though. For instance, at least in Haskell, a "strict function" is often defined as one that forces its arguments sufficiently that the function will evaluate to _|_ ("bottom") whenever any argument does; note that by this definition,
id
is strict (in a trivial sense), because forcing the result ofid x
will have exactly the same behavior as forcingx
alone.这开始是一个更新,但开始变得很长。
懒惰/按需调用是 call 的记忆版本-by-name 其中,如果对函数参数进行求值,则存储该值以供后续使用。在“纯粹”(无效果)设置中,这会产生与按名称调用相同的结果;当函数参数使用两次或多次时,按需调用几乎总是更快。
命令式示例 - 显然这是可能的。有一篇有趣的文章 惰性命令式语言。它说有两种方法。一种需要闭包,第二种使用图缩减。由于 C 不支持闭包,因此您需要显式地将参数传递给迭代器。您可以包装一个映射结构,如果该值不存在,则计算它,否则返回值。
注意:Haskell 通过“指向代码的指针在第一次执行时被值替换”来实现这一点 - luqui。
这是非严格的点名方式,但会共享/记忆结果。
按名称调用 - 在按名称调用求值中,函数的参数在调用函数之前不会求值,而是直接替换到函数体中(使用 capture -避免替代)然后每当它们出现在函数中时就对其进行评估。如果函数体中未使用参数,则永远不会计算该参数;如果多次使用,则每次出现时都会重新评估。
命令式示例:回调
注意:这是非严格的,因为如果不使用它会避免评估。
非严格 = 在非严格求值中,函数的参数不会被求值,除非它们实际用于函数体的求值。
命令式示例:短路
注意:_|_ 似乎是测试函数是否非严格的一种方法
因此函数可以是非严格的,但不能是惰性的。惰性函数始终是非严格的。
Call-By-Need 部分由 Call-By-Name 定义,而 Call-By-Name 部分由 Non-Strict
定义href="http://webcache.googleusercontent.com/search?q=cache%3ao7wQ-sooFyUJ%3aciteseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.35.1663& ;rep=rep1&type=ps%20%22call%20by%20need%22%20imperative%20language&cd=3&hl=en&ct=clnk&gl=ca&source=www.google.ca" rel="nofollow noreferrer">"惰性命令式语言"
This started out as an update but it started to get long.
Laziness / Call-by-need is a memoized version of call-by-name where, if the function argument is evaluated, that value is stored for subsequent uses. In a "pure" (effect-free) setting, this produces the same results as call-by-name; when the function argument is used two or more times, call-by-need is almost always faster.
Imperative Example - Apparently this is possible. There is an interesting article on Lazy Imperative Languages. It says there are two methods. One requires closures the second uses graph reductions. Since C does not support closures you would need to explicitly pass an argument to your iterator. You could wrap a map structure and if the value does not exist calculate it otherwise return value.
Note: Haskell implements this by "pointers to code which are replaced with a value the first time they are executed" - luqui.
This is non-strict call-by-name but with sharing/memorization of the results.
Call-By-Name - In call-by-name evaluation, the arguments to a function are not evaluated before the function is called — rather, they are substituted directly into the function body (using capture-avoiding substitution) and then left to be evaluated whenever they appear in the function. If an argument is not used in the function body, the argument is never evaluated; if it is used several times, it is re-evaluated each time it appears.
Imperative Example: callbacks
Note: This is non-strict as it avoids evaluation if not used.
Non-Strict = In non-strict evaluation, arguments to a function are not evaluated unless they are actually used in the evaluation of the function body.
Imperative Example: short-circuiting
Note: _|_ appears to be a way to test if a function is non-strict
So a function can be non-strict but not lazy. A function that is lazy is always non-strict.
Call-By-Need is partly defined by Call-By-Name which is partly defined by Non-Strict
An Excerpt from "Lazy Imperative Languages"
按照我的理解,“非严格”意味着试图通过以较少的工作量完成来减少工作量。
而“惰性评估”和类似尝试通过避免完全完成(希望永远)来减少总体工作量。
从你的例子来看......
这个表达式的短路不可能导致触发“未来的工作”,并且推理中既没有投机/摊销因素,也没有任何计算复杂性债务的累积。
而在 C# 示例中,“惰性”在整体视图中保留了函数调用,但作为交换,却带来了上述各种困难(至少从调用点到可能完全完成......在这段代码中,这是一个可以忽略不计的-距离路径,但想象一下这些函数有一些高争用的锁需要忍受)。
The way I understand it, "non-strict" means trying to reduce workload by reaching completion in a lower amount of work.
Whereas "lazy evaluation" and similar try to reduce overall workload by avoiding full completion (hopefully forever).
from your examples...
...short circuiting from this expression would not possibly result in triggering 'future work', and there's neither a speculative/amortized factor to the reasoning, nor any computational complexity debt accruing.
Whereas in the C# example, "lazy" conserves a function call in the overall view, but in exchange, comes with those above sorts of difficulty (at least from point of call until possible full completion... in this code that's a negligible-distance pathway, but imagine those functions had some high-contention locks to put up with).
如果我们谈论一般的计算机科学术语,那么“惰性”和“非严格”通常是同义词——它们代表相同的整体想法,在不同的情况下以不同的方式表达自己。
然而,在给定的特定专业背景下,它们可能具有不同的技术含义。我认为您无法准确和普遍地描述在需要做出改变的情况下“懒惰”和“非严格”之间的区别。
If we're talking general computer science jargon, then "lazy" and "non-strict" are generally synonyms -- they stand for the same overall idea, which expresses itself in different ways for different situations.
However, in a given particular specialized context, they may have acquired differing technical meanings. I don't think you can say anything accurate and universal about what the difference between "lazy" and "non-strict" is likely to be in the situation where there is a difference to make.