共享无点功能,但评估两次

and*_*e_c 15 haskell lazy-evaluation ghci

我一直在尝试了解Haskell中共享计算的工作原理。根据我的理解,无点共享计算应该只进行一次评估(由CSE提供)。

(A)例如,考虑以下代码及其输出:

*Main> let foo = trace "eval foo" 5 in foo + foo
eval foo
10

*Main> let foo' = \x -> trace "eval foo'" x in (foo' 5) + (foo' 5)
eval foo'
eval foo'
10
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,foo仅被评估一次(CSE可能会启动),而foo'被懒惰地评估两次。那样就好。我使用GHCi 7.6.3版尝试了上述方法。接下来,我在GHCi版本8.6.5中尝试了相同的代码,而是产生了以下结果:

*Main> let foo = trace "eval foo" 5 in foo + foo
eval foo
eval foo
10
Run Code Online (Sandbox Code Playgroud)

请注意,该foo值现在已评估两次。

(B)同样,对于GHCi版本7.6.3:

*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
10
Run Code Online (Sandbox Code Playgroud)

但是,GHCi版本8.6.5评估goo两次:

*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
eval goo
10
Run Code Online (Sandbox Code Playgroud)

(C)最后,两个版本在以下方面产生相同的结果:

*Main> let foo_wrapper x = let foo = trace "eval foo" x in foo + foo
*Main> foo_wrapper 5
eval foo
10
Run Code Online (Sandbox Code Playgroud)

我想知道GHCi-8是否默认关闭了某些优化功能,或者是否以某种方式两次评估了trace力的副作用foo?还是GHCi-7有问题?GHCi应该如何与(A)和(B)这样的表达式一起表现?

(更新1)

对于情况(C),请考虑在GHCi-8中进行以下运行(主要区别在于的第二个参数trace):

*Main> let foo_wrapper x = let foo = trace "eval foo" x in foo + foo 
*Main> foo_wrapper 5
eval foo
10
*Main> :t (foo_wrapper 5)
(foo_wrapper 5) :: Num a => a

*Main> let foo_wrapper' x = let foo = trace "eval foo" 5 in foo + foo 
*Main> foo_wrapper' ()
eval foo
eval foo
10
*Main> :t (foo_wrapper' ())
(foo_wrapper' ()) :: Num a => a
Run Code Online (Sandbox Code Playgroud)

lef*_*out 14

如预期的那样,foo仅评估一次(CSE可能会启动)

不,这与CSE无关,它只是惰性评估(又称按需调用)的工作方式:foo是一种恒定的应用形式,因此只需要一次计算(从thunk转换为WHNF),然后可以简单地进行计算无需进一步计算即可重复使用。之所以在GHCi-8中不再起作用,是因为7.8消除了GHCi中的单态性限制。为什么这是相关的?好吧,trace "eval foo" 5是type的多态表达式Num a -> a。多态表达式不能是CAF。因此,您不必按需呼叫,而可以按名称呼叫语义。

再次共享的最简单方法是通过使类型变为单态并添加显式签名来实施CAF:

Prelude Debug.Trace> let foo = trace "eval foo" 5 :: Int in foo + foo
eval foo
10
Run Code Online (Sandbox Code Playgroud)

  • @andre_c您是错误的:`let foo = trace“ eval foo” x`不是多态绑定。它是单态的;foo必须具有调用方为x选择的确切类型。(注意,整个函数是多态的,但该子项不是!)例如,`let foo = trace“ eval foo” 5; y :: Int; y = foo; z ::双; z = foo`很好,因为`foo`是一个多态绑定;但是`let foo = trace“ eval foo” x; y :: Int; y = foo; z ::双; z = foo`是类型错误。 (2认同)