共享无点函数,但计算两次

A point-free function is shared, yet evaluated twice

我一直在努力了解共享计算在 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

正如预期的那样,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

请注意 foo 现在计算了两次。

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

*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
10

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

*Main> let goo = const (trace "eval goo" 5) in goo () + goo ()
eval goo
eval goo
10

(C) 最后,两个版本对以下内容产生相同的结果:

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

我想知道 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

As expected, foo is evaluated only once (CSE probably kicks in)

不,这与 CSE 无关,这只是延迟评估(又名 按需调用)的工作方式:fooconstant applicative form, as such it just needs to be computed (forced from a thunk to WHNF) once and can then simply be reused without any further computation. The reason that this doesn't work in GHCi-8 anymore is that 7.8 has removed the monomorphism restriction在 GHCi 中。为什么这是相关的?那么,trace "eval foo" 5 是类型 Num a -> a 的多态表达式。并且多态表达式可以 而不是 是 CAF。因此,您得到的不是按需调用,而是按名称调用语义。

再次获得共享的最简单方法是通过添加显式签名使类型成为单态来强制执行 CAF:

Prelude Debug.Trace> let foo = trace "eval foo" 5 :: Int in foo + foo
eval foo
10