我正试图了解Haskell中懒惰评估方面的问题。
如果我有一个函数调用,如:
negate $ 5 * sqrt 16
我的理解是Haskell将首先处理sqrt 16
,创建一个thunk,允许在需要时计算值。
但sqrt 16
是在传递给乘法时还是仅在以某种方式输出到控制台时进行评估?
换句话说,当输入GHCi时,表达式的每个部分将以什么顺序进行评估?(例如)?
答案 0 :(得分:8)
默认情况下,每个函数和构造函数调用都会成为一个thunk。因此,在这种情况下,评估会发生如下:
evaluate "negate $ 5 * sqrt 16" -> <thunk> $ <thunk>
evaluate "negate" -> <func>
evaluate "5 * sqrt 16" -> <thunk> * <thunk>
evaluate "5" -> 5.0
evaluate "sqrt 16" -> 4.0
<thunk>
表示thunk,<func>
表示它是一个无法表示为字符串的函数值。
缩进意味着“父级”将在评估“子级”之前对其进行评估。
因此,如果您编写print (negate $ 5 * sqrt 16)
,运行时将执行以下步骤:
eval thunk:
<thunk 1> $ <thunk 2>
eval thunk 1:
<func> $ <thunk 2>
eval thunk 2:
<func> $ <thunk 3> * <thunk 4>
(cheating a little here, because (*) is strict, so these three are actually
one step:)
eval thunk 3:
<func> $ 5 * <thunk 4>
eval thunk 4 by applying sqrt:
<func> $ 5 * 4
apply (*):
<func> $ 20
apply ($):
-20
答案 1 :(得分:6)
您可以将其视为外向内,即调用第一个negate
。然后它将强制评估其论点,这可能会迫使评估其他表达式等等。您可以使用Debug.Trace.trace
来打印其第一个参数,同时在评估时返回第二个参数,以查看GHCi中发生的事件的确切顺序:
> trace "A" (negate (trace "B" (5 * (trace "C" (sqrt 16)))))
A
B
C
-20.0
但请注意,允许编译器执行可能更改表达式求值顺序的优化,这就是我们在订单重要时使用IO
monad的原因。
答案 2 :(得分:4)
当需要该值时,将计算表达式。因此,如果你写:
f = negate $ 5 * sqrt 16
直到您使用f
进行评估。 negate
需要5 * sqrt 16
,而sqrt 16
则需要{{1}}。评估继续展开,直到它到达将被评估的基本情况,然后将用于前一个/更高的表达式(现在向后),直到评估整个表达式。
答案 3 :(得分:4)
首先,创建整个表达式的thunk。 *
是严格的,因此sqrt 16
的thunk可能会也可能不会在其中创建,具体取决于优化(可能会使用对sqrt
的直接调用)。然后当它被强制(需要它的值)时,negate
将强制其参数,即*
表达式,并且严格,*
将强制它的两个参数并产生值20
。
顺便说一下,当你谈到Haskell时,你应该谈论的是非严格的语义。 “Thunk”和“lazy”属于实现细节。
答案 4 :(得分:2)
我在这方面读到的真正帮助我的方法就是这样看待它:
因此,通过在控件上调用适当的show
方法将值打印到控制台;即,打印到控制台会强制使用show x
形式的表达式(对于某些x
)。迫使show x
部队x
。假设x
是negate $ 5 * sqrt 16
;由于negate
在其参数中是严格的,因此强制thunk也会强制5 * sqrt 16
的thunk。同样,*
和sqrt
在其参数中都是严格的,因此也必须强制5
,sqrt 16
和16
的thunk。
要理解的另一件事是数据构造函数和模式匹配如何影响thunking。基本上,除非有特殊的严格性,否则构造函数就像非严格函数一样,强制thunk不会强制构造函数的参数。除非使用特殊的惰性模式匹配语法,否则对构造函数的匹配会强制参数thunk。所以我们有:
identity x = x -- irrefutable pattern; `x` is not forced
uncons (x:xs) = (x, xs) -- match against (:) constructor; argument
-- must be forced, but x and xs aren't forced
foo (x:x':xs) = (x, x', xs) -- two matches against (:) constructor;
-- the argument thunk is forced, as is its
-- tail thunk.