我是Haskell的新手,我不明白在函数中定义where
部分时会发生什么。
例如,在以下函数中
f x = y + y
where y = product [0..x]
我不明白y
是否仅被product [0..x]
替换并计算两次,或者product [0..x]
计算一次,其结果保存在类似{{{}的变量中1}}然后再做总和。
如果计算两次会不会效率低下?
答案 0 :(得分:7)
Haskell是纯粹的,如果你想在任何你喜欢的地方内联y
那么值是相同的
y + y where y = product [0..x] == product [0..x] + product [0..x]
但是如果需要,Haskell允许其实现选择更快的执行路径。特别是,Haskell允许一个懒惰(不同于Haskell 要求其实现的“非严格”语义)计算方法
y + y where y = product [0..x] -- where-let transform
==
let y = product [0..x] in y + y -- store 'y' on heap
==
{y = THUNK} y + y -- (+) forces its arguments
-- let's assume x = 6
==
{y = product [0..6]} y + y -- we still need `y`, so eval
==
{y = 0} y + y -- replace
==
0 + 0
==
0
正如您所看到的,实现此代码的一种方法是将y
堆中的值作为THUNK
,延迟值,然后根据需要计算并使用最终计算价值在需要y
的地方。这被称为惰性图缩减语义,而且确实是GHC实现的。
请注意,GHC在技术上可以做相反的事情,转换
product [0..x] + product [0..x]
到
let y = product [0..x] in y + y
并像以前一样执行它,保存一些计算。优化编译器可以寻找这样的机会,但它必须克制!在解除像这样的常见子表达式时,让编译器生成的代码执行得更糟(空间泄漏)非常容易。
因此,虽然GHC将使用您直接编写的名称来保存重复计算,但它不可能单独消除常见的子表达式。
如有疑问,请使用let
或where
!
答案 1 :(得分:3)
这会将值绑定到名称y
,然后在定义中使用它两次。你的直觉是正确的,计算它两次是低效的,如果你把它定义为
f x = product [0..x] + product [0..x]
然而,GHC可能会对此进行优化,但可能没有 Apparently not. -O2
。然而,Haven没有测试过这个理论。