haskell - 共享一个无点函数,但评估两次
问题描述
我一直在尝试了解 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
trace
我想知道 GHCi-8 中是否默认关闭了某些优化,或者是否以某种方式对力的副作用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
解决方案
正如预期的那样,
foo
只评估一次(CSE 可能会启动)
不,这与 CSE 无关,它只是惰性求值(又名按需调用)的工作方式:foo
是一种恒定的应用形式,因此它只需要计算一次(从 thunk 强制到 WHNF),然后就可以简单地无需任何进一步计算即可重复使用。这在 GHCi-8 中不再起作用的原因是 7.8 取消了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
推荐阅读
- r - 如何在 data.tables 中使用 i 根据条件选择所有列的行
- sass - 在语义上构建基础 XY 网格时如何为每个断点定义网格边距
- html - 我是 Github 的新手,index.html 现在显示在 github 上,我做错了什么?
- android - 安装没有成功。无法安装应用程序。未找到设备
- forms - 当我使用文件输入类型时,我无法以 html 格式提交表单,我将其部署为只能访问我的网络应用程序
- javascript - 使用 REST API(使用 JS)访问 Firebase 动态链接的错误请求
- jenkins - 如何在 Jenkins Pipeline 中手动选择环境
- c - 将枚举存储在有符号字符中并在 C 中与枚举进行比较失败
- jprofiler - JProfiler:内存分析需要帮助 - 我在哪里可以找到分配对象的行号
- c++ - Visual Studio 删除共享的 .pch 文件,以及有关自定义构建步骤的问题