哈斯克尔:非严格和懒惰有什么不同?
我经常看到懒惰和非严格不一样,但是我发现很难理解这个差别。 它们似乎可以互换使用,但是我知道它们有不同的含义。 我将不胜感激有助于理解差异。
我有几个分散在这个职位的问题。 我将在这篇文章结尾总结这些问题。 我有几个例子片段,我没有testing它们,只是把它们作为概念呈现出来。 我已经添加了引号,以防止您查找它们。 也许这会在以后同样的问题上帮助别人。
非严格防卫:
函数f被认为是严格的,如果应用于非终止expression式时,它也不能终止。 换句话说,如果f bot的值是| ,则f是严格的 。 对于大多数编程语言,所有function都是严格的。 但在Haskell中并非如此。 作为一个简单的例子,考虑const1,常量1函数,定义如下:
const1 x = 1
在Haskell中const1 bot的值是1.在操作上,因为const1不需要它的参数的值,所以它永远不会试图评估它,因此永远不会被捕获到一个非终结的计算。 由于这个原因,非严格函数也被称为“懒惰函数”,并被认为是“懒惰”或“需要”评价他们的论点。
– 对Haskell的一个简单介绍:函数
我非常喜欢这个定义。 这似乎是最好的,我可以find理解严格。 是const1 x = 1
懒吗?
非严格意味着减less(评估的math术语)从外部进行,
所以如果你有(a +(b c))那么你先减less+,然后你减less内部(b c)。
– Haskell维基:Lavy vs非严格
Haskell Wiki真的让我困惑。 我明白他们在说什么,但是我不明白(a+(b*c))
是如何评价非严格的,如果是通过_|_
?
在非严格评估中,除非函数实际用于评估函数体,否则不会评估函数的参数。
在教会编码下,运营商的懒惰评估映射到function的非严格评估; 为此,非严格评价通常被称为“懒惰”。 许多语言中的布尔expression式都使用一种非严格评估forms,称为短路评估,只要能够确定将产生一个明确的布尔值就会返回评估结果 – 例如,在遇到真值的分离expression式中,或者在遇到错误的连接expression式等等。 条件expression式通常也使用懒惰评估,只要一个明确的分支会导致评估返回。
– 维基百科:评估策略
懒惰Def:
另一方面,懒惰评价意味着只有在需要结果的时候评价一个expression(注意从“减less”转变为“评价”)。 所以当评估引擎看到一个expression式时,它会build立一个thunk数据结构,其中包含评估expression式所需的任何值以及一个指向expression式本身的指针。 当实际需要结果时,评估引擎将调用expression式,然后将结果replace为结果以备将来参考。 …
很明显,在一个强硬的和一个被部分评价的expression之间存在很强的对应关系。 因此在大多数情况下,术语“懒惰”和“非严格”是同义词。 但不完全。
– Haskell维基:Lavy vs非严格
这似乎是一个Haskell的具体答案。 我把那种懒惰的手段和非严格的手段进行部分的评价。 这个比较是否简化了? 懒惰总是意味着thunk和non-strict总是意味着部分的评价。
在编程语言理论中,懒惰评价或按需呼叫1是一种评价策略,它延迟expression式的评价直到其实际需要(非严格评价),并且避免重复评价(共享)。
– 维基百科:懒惰评估
强制性的例子
我知道大多数人在学习function语言时都忘记了命令式编程。 但是,我想知道这些是非严格的,懒惰的,还是两者兼而有之? 至less它会提供一些熟悉的东西。
短路
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; } }
– MSDN:yield(c#)
callback
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
也懒吗? - 如何从“内向”非严格评估? 是否因为内向允许减less不必要的expression式,如在
const1 x = 1
? 减less似乎符合懒惰的定义。 - 懒惰总是意味着thunk和non-strict总是意味着部分的评价 ? 这只是一个泛化?
- 懒惰,非严格,两者还是两者都是下面的必要概念?
- 短路
- 使用收益
- 通过回叫来延迟或避免执行
- 懒惰 是非严格的一个子集,反之亦然,或者它们是相互排斥的。 比如说可以不严格不严格 ,也可以不严格而不严格 ?
- 哈斯克尔的非严格性是否由懒惰来实现?
谢谢你这么!
非严格和懒惰,虽然非正式可互换,适用于不同的讨论领域。
非严格指语义 :expression式的math意义。 非严格适用的世界没有function运行时间,内存消耗甚至计算机的概念。 它只是简单地谈论域中的哪些值映射到共域中的哪些types的值。 特别的,一个严格的函数必须把值⊥(“bottom” – 见上面关于这个的语义链接)映射到⊥; 一个非严格的function是不允许的。
懒惰是指操作行为:代码在真实计算机上执行的方式。 大多数程序员在操作上会想到程序,所以这可能就是你所想的。 懒惰评估是指使用thunks的实现 – 指向代码的指针,在第一次执行时用值replace。 注意这里的非语义词:“指针”,“第一次”,“执行”。
懒惰的评估会产生非严格的语义,这就是为什么概念看起来如此接近。 但正如FUZxxl指出的那样,懒惰不是实现非严格语义的唯一方法。
如果您有兴趣了解更多关于这个区别的信息,我强烈推荐上面的链接。 读它是我对计算机程序意义的一个转折点。
评估模型的一个例子,既不严格也不懒惰: 乐观的评估 ,它提供了一些加速,因为它可以避免很多“简单”的thunk:
乐观评估意味着,即使一个子expression式可能不需要评估超expression,我们仍然使用一些启发式评估它的一些。 如果子expression式没有足够快地终止,我们暂停评估,直到真正需要为止。 如果稍后需要子expression式,这给了我们一个优于惰性评估的优势,因为我们不需要生成一个thunk。 另一方面,如果expression没有终止,我们不会失去太多,因为我们可以很快地放弃它。
正如你所看到的,这个评估模型并不严格 :如果产生_ | _的东西被评估,但是不需要,那么当发动机中止评估时,该function仍将终止。 另一方面,可能有更多的expression比需要的评估,所以它不是完全懒惰 。
是的,这里使用的术语有一些不明确的地方,但是在大多数情况下,术语是一致的,所以这不是太大的问题。
一个主要区别是条件评估 。 对此有多种策略,范围从“尽快”到“仅在最后一刻”。 渴望评价一词有时用于倾向于前者的策略,而懒惰评价恰当地指的是倾向于后者的一系列策略。 “懒惰评估”和相关策略之间的区别往往涉及评估某些事物的结果在何时何地被保留,而不是被抛在一边。 Haskell中熟悉的为数据结构分配名称并将其编入索引的memoization技术就是基于此。 相反,一种简单地将expression式彼此拼接在一起的语言(如“按名称”评估)可能不支持这一点。
另一个区别是评估哪些术语 ,从“绝对一切”到“尽可能less”。 由于实际用于计算最终结果的任何值都不能被忽略,因此这里的差异是多less多余的术语被评估。 除了减less程序的工作量之外,忽略不使用的术语意味着它们所产生的任何错误都不会发生。 当划分时, 严格性指的是评估所有事情的性质(在严格的function的情况下,例如,这意味着它所应用的术语, 并不一定是指参数内的子expression式) 而非严格意味着只评估一些事情(通过延迟评估或完全放弃条款)。
应该很容易地看到这些以复杂的方式相互作用。 决定根本不是正交的,因为极端往往是不相容的。 例如:
-
非严格的评估排除了某种程度的渴望; 如果你不知道是否需要一个术语,你还不能评估它。
-
非常严格的评估使得不积极有点不相干; 如果你正在评估一切,那么什么时候做这个决定是不太重要的。
虽然有其他的定义存在。 举例来说,至less在Haskell中,“严格函数”通常被定义为强制其参数足以使函数评估为| 每当有任何争论时, 请注意,通过这个定义, id
是严格的(在一个微不足道的意义上),因为强制id x
的结果将具有与单独强制x
完全相同的行为。
这开始是一个更新,但它开始变长。
Laziness / Call-by-need是通过名称调用的memoized版本,其中,如果函数参数被评估,那么该值被存储用于后续使用。 在“纯”(无效)设置中,这将产生与名称相同的结果; 当函数参数使用两次或更多次时,按需呼叫几乎总是更快。
强制性的例子 – 显然这是可能的。 懒惰的祈祷语言有一篇有趣的文章。 它说有两种方法。 一个需要closures第二个使用图减less。 由于C不支持闭包,所以你需要显式的传递一个参数给你的迭代器。 你可以包装一个地图结构,如果该值不存在,则计算它,否则返回值。
注意 :Haskell通过“指向第一次执行时replace为值的代码的指针”来实现这一点 – luqui。
这是非严格的名义呼叫,但分享/记忆的结果。
按名称调用 – 在名称调用中,函数的参数在调用函数之前不会被评估 – 而是直接replace为函数体(使用捕获 – 避免replace),然后保留只要它们出现在函数中就进行评估。 如果在函数体中没有使用参数,则从不计算参数; 如果多次使用,则每次出现时都要重新评估。
命令示例:callback
注意:这是非严格的,因为如果不使用,它会避免评估。
Non-Strict =在非严格评估中,除非函数实际用于评估函数体,否则不会评估函数的参数。
强制性示例 :短路
注意 :_ |似乎是一种testing函数是否非严格的方法
所以一个函数可以是非严格的,但不是懒惰的。 一个懒惰的函数总是非严格的。 “按需 拨号”部分由“ 非 名字”部分定义的“按名称”定义
摘要来自“懒惰的祈祷语言”
2.1。 非严格的语义VS. 懒惰评价首先要澄清“非严格语义”和“懒惰评价”的区别。 非严格语义是那些指定直到基本操作需要expression式才被评估的那些语义。 可能有各种types的非严格语义。 例如,非严格的过程调用donot评估参数,直到它们的值是必需的。 数据构造函数可能有非严格的语义,其中复合数据由未评估的片断组装。懒惰评估也称为延迟评估,是通常用于实现非严格语义的技术。 在第4节中,通常用于执行懒惰评估的两种方法是非常简短的。
通过价值调用,懒惰调用,以及按名称调用“按值调用”是用于具有严格语义的过程调用的通用名称。 在通过valuelanguages的调用中,过程调用的每个参数在进行过程调用之前被评估; 然后将值传递给程序或包含expression式。 按价值调用的另一个名称是“渴望”评估。按价值调用也称为“应用顺序”评估,因为所有参数都在函数应用于它们之前进行评估。“懒惰调用”(使用威廉·克林格的术语[8 ])是使用非严格语义的过程调用的名称。 在通过惰性过程调用调用的语言中,参数在被replace到过程体之前不被评估。 由于评估expression式的顺序(从最外层到最内层,从左到右),所以懒惰评估调用也被称为“正常顺序”评估。“按名称调用”是懒惰调用的特定实现,在Algol中使用-18 [18]。 Algol-60的devise者希望在名称参数被评估之前,将名称参数在物理上代入程序主体,并用圆括号括起来,并用适当的名称改变来避免冲突。
打电话给懒惰VS. CALL BY NEED需求调用是懒惰调用的扩展,通过观察提示懒惰评估可以通过记住给定延迟expression式的值(一旦强制)来优化,以便如果需要再次需要时不需要重新计算该值。 通过需求评估来调用,因此,通过使用记忆来延长懒惰方法的调用,以避免重复评估的需要。 弗里德曼和怀斯是需求评估的最早接受者之一,他们提出“自杀暂停”,在他们初次评估时自我毁灭,用自己的价值取代自己。
如果我们谈论一般的计算机科学术语,那么“懒惰”和“非严格”通常是同义词 – 它们代表着相同的整体思想,它以不同的方式以不同的方式expression。
但是,在特定的特定环境下,它们可能具有不同的技术含义。 我不认为你可以说任何关于“懒惰”和“非严格”之间在区别上有什么不同的情况下的准确和普遍性。