我正在努力学习Haskell,但我被困在理解lazy evaluation上。有没有人可以详细地给我解释一下惰性评估和下面两个案例的输出,以及与下面给出的相关解释
伪代码:
x = keyboard input (5)
y = x + 3 (=8)
echo y (8)
x = keyboard input (2)
echo y案例1:静态绑定,延迟计算
案例2:动态绑定,惰性计算。
我需要知道最后一行(echo y)是什么将print...in上述两个案例。
发布于 2011-10-15 07:40:06
抱歉这太长了但是..。
我担心答案将在很大程度上取决于单词的意思……
首先,这是Haskell中的代码(它使用静态绑定和延迟求值):
readInt :: String -> Int
readInt = read
main = do
x <- fmap readInt getLine
let y = x + 3
print y
x <- fmap readInt getLine
print y它打印8和8。
这是R中的代码,它使用了惰性计算和一些人所说的动态绑定:
delayedAssign('x', as.numeric(readLines(n=1)))
delayedAssign('y', x + 3)
print(y)
delayedAssign('x', as.numeric(readLines(n=1)))
print(y)它打印8和8。没什么不同的!
现在在C++中,它使用了严格的计算和静态绑定:
#include <iostream>
int main() {
int x;
std::cin >> x;
int y = x + 3;
std::cout << y << "\n";
std::cin >> x;
std::cout << y << "\n";
}它打印8和8。
现在让我告诉你我认为这个问题的实际意义是什么;)
“懒惰评估”可以有很多不同的含义。在Haskell中,它有一个非常特殊的含义,即在嵌套表达式中:
f (g (h x))评估的工作原理就像在g (h x)之前评估f一样,即评估是“->外的”。实际上,这意味着如果f看起来像
f x = 2ie只是抛弃了它的论点,g (h x)永远不会被评估。
但我认为这并不是“懒惰评估”的问题所在。我认为这是因为:
+总是计算它的参数!无论您是否使用惰性计算,+都是一样的。keyboard input --这并不是真正的计算,因为它会导致一个操作发生;也就是说,它从用户读取。Haskell人员通常不会称之为“懒惰计算”--他们会称之为懒惰(或延迟)执行。
那么,懒惰的执行对你的问题意味着什么呢?这将意味着动作keyboard input会被延迟...直到真正需要值x。在我看来,这种情况就发生在这里:
echo y因为此时您必须向用户显示一个值,因此您必须知道x是什么!那么懒惰执行和静态绑定会发生什么呢?
x = keyboard input # nothing happens
y = x + 3 # still nothing happens!
echo y (8) # y becomes 8. 8 gets printed.
x = keyboard input (2) # nothing happens
echo y # y is still 8. 8 gets printed.现在关于“动态绑定”这个词。它可以有不同的含义:
y的公式是x + 3),直到变量被求值时才会被检查。我猜这就是你问题中“动态绑定”的意思。使用动态绑定(sense 2)和惰性执行再次检查代码
x = keyboard input # nothing happens
y = x + 3 # still nothing happens!
echo y (8) # y becomes 8. 8 gets printed.
x = keyboard input (2) # nothing happens
echo y # y is already evaluated,
# so it uses the stored value and prints 8我不知道有哪种语言会在最后一行打印7 ...但我真的认为这就是问题所希望发生的事情!
发布于 2011-12-07 06:21:37
在Haskell中延迟求值的关键是它根本不会影响程序的输出。您可以阅读它,就好像所有内容都在定义之后立即进行了评估一样,但您仍然会得到相同的结果。
惰性求值只是一种策略,用于计算程序中表达式的值。有很多种可能,它们都给出了相同的result1;任何改变程序含义的评估策略都不是有效的策略!
因此,从某种角度来看,如果懒惰计算给你带来了麻烦,你还不需要理解它。当您在学习Haskell时,尤其是如果它是您的第一个函数式纯语言,考虑以这种方式表达自己要重要得多。我也认为训练自己适应阅读Haskell的语法(通常是相当密集的)比完全“摸索”懒惰的评估更重要。所以,如果这个概念给你带来了困难,也不用太担心。
这就是说,我的解释如下所示。我没有使用您的示例,因为它们并不真正受到懒惰计算的影响,并且Owen已经比我更清楚地讨论了动态绑定和延迟执行。
(有效)评估策略之间最重要的区别是,一些策略可能根本无法返回结果,而另一种策略可能会成功。懒惰评估有一个特殊的特性,如果有任何(有效的)评估策略可以找到结果,懒惰评估就会找到它。特别是,生成无限数据结构,然后只使用有限数量的数据的程序可能会以惰性计算终止。在您可能习惯的严格计算中,程序必须完成无限数据结构的生成,然后才能继续使用它的一部分,当然它会使用它。
惰性评估实现这一点的方法是,只在需要时评估一些东西,以确定下一步要做什么。当您调用返回列表的函数时,它会立即“返回”,并为列表提供一个占位符。这个占位符可以传递给其他函数,存储在其他数据结构中,任何东西。只有当程序需要了解列表的某些信息时,才会对其进行实际评估,并且仅限于需要的程度。
假设程序现在要做一些不同的事情,如果列表为空,而不是空的。进一步计算最初返回占位符的函数调用,看看它是返回一个空列表还是返回一个带有head元素的列表。然后评估再次停止,因为程序现在知道该走哪条路。如果列表的其余部分永远不需要,那么它将永远不会被计算。
但它的评估次数也不会超过需要的次数。如果占位符被传递到多个函数中(因此它现在涉及到其他尚未求值的函数调用),或者存储到几个不同的数据结构中,Haskell仍然“知道”它们都是相同的东西,并安排它们都“看到”由其中任何一个触发的占位符的任何进一步求值的效果。最终,如果在某个地方需要所有列表,它们都将指向一个普通的经过充分评估的数据结构,懒惰不会产生进一步的影响。
但要记住的关键一点是,在生成占位符时,生成该列表所需的所有内容都已经确定和修复。It 不能被程序中发生的任何事情所影响。如果不是这样的话,Haskell就不是纯了。反之亦然;不纯的语言不能在幕后有Haskell风格的完全懒惰,因为你得到的结果可能会发生巨大的变化,这取决于在未来需要结果的时间。相反,支持惰性求值的不纯语言倾向于只对程序员明确声明的某些事情进行惰性求值,手册中警告说“不要在依赖于副作用的东西上使用惰性”。
1.我在这里躺了一会儿。继续阅读下面这条线,看看为什么。
发布于 2011-12-07 05:34:14
Haskell中的惰性评估:最左-最外+图归约
正方形x=x*x
正方形(正方形42)
(正方形42) *(正方形42)由于图形简化,->正方形42将仅计算一次
(42 * 42) *(正方形42)
(1764) *(平方42) ->下一步是图形归约
1764 * 1764
=3111696
最左边-最里面(Java、C++)
正方形(正方形42)
正方形( 42 * 42)
square ( 1764 )
1764 * 1764 =3111696
https://stackoverflow.com/questions/7774490
复制相似问题