【问题标题】:How does argument passing order affect lazy evaluation in Haskell?参数传递顺序如何影响 Haskell 中的惰性求值?
【发布时间】:2017-01-22 13:40:08
【问题描述】:

我一直在尝试理解 Haskell 中的惰性求值,我基本上将其理解为仅在需要时才进行求值。但是当试图有效地实现斐波那契时,我遇到了这个(奇怪的?)行为: 这个实现:

--wrapper function used by both implementations
fib :: Int -> Int
fib x = if x < 0 then 0 else fib2 0 1 x

fib2 :: Int -> Int -> Int -> Int
fib2 x y 0 = x
fib2 x y z = fib2 y (x + y) (z - 1)

即使使用

调用也能正常工作
fib 20000000
> -70318061090422843

在递归调用中交换传递的参数时:

fib2 :: Int -> Int -> Int -> Int
fib2 x y 0 = x
fib2 x y z = fib2 (x + y) x (z - 1)

结果:

fib 20000000
>*** Exception: stack overflow

为什么我不必告诉编译器在第一个示例中急切地求值? 为什么第二个例子不起作用,而第一个例子起作用?

为此,我在 Windows 10 上使用了 GHCi 8.0.1。

【问题讨论】:

  • 不确定这种情况(可能第一个版本基本上展开为一个循环,而第二个版本继续分配堆栈帧)请参阅stackoverflow.com/questions/13042353/… 以了解类似情况...
  • 我感觉它一定与柯里化有关,因为溢出示例在第一个参数中建立了 thunk。
  • 请包含 ghci 版本信息。对我来说,这不会发生,而是分配失败(7.6.3)。
  • 好吧,就我而言,两者都已经是 TCO,不是吗?在这些答案中,它甚至特别指出要使累积论点严格,对于第一个示例我没有这样做,它仍然有效。这实际上是我困惑的根源。

标签: haskell recursion lazy-evaluation


【解决方案1】:

首先,请注意您的两个版本之间的差异是定量的,而不是定性的。第一个将在40000000 的输入上堆栈溢出,第二个将在10000000 的输入上成功完成。看起来第二个版本使用的堆栈量是第一个版本的两倍。

基本上,原因是,如果我们为 xy 参数中的 thunk 引入符号 {n},那么您的第一个版本会这样做

fib2 {n} {n+1} 0 = {n}
fib2 {n} {n+1} z = let {n+2} = (+) {n} {n+1}    -- build a thunk
                    in fib2 {n+1} {n+2} (z - 1)

第二个版本可以

fib2 {n+1} {n} 0 = {n+1}
fib2 {n+1} {n} z = let {n+2} = (+) {n+1} {n}    -- build a thunk
                    in fib2 {n+2} {n+1} (z - 1)

现在考虑当fib2 递归完成时会发生什么,是时候评估{n}(或{n+1};让我们忽略这个差异)。每个 thunk {0}, ..., {n} 只会按某种顺序被评估一次。碰巧(+) 首先评估它的左参数,然后是它的右参数。为了明确起见,让我们以n = 6 为例。评价看起来像

{6} = (+) {4} {5}
... {4} = (+) {2} {3}
... ... {2} = (+) {0} {1}
... ... ... {0} = 0
... ... ... {1} = 1
... ... {2} = 1
... ... {3} = (+) {1} {2}
... ... ... {1} = 1
... ... ... {2} = 1   -- we already calculated it
... ... {3} = 2
... {4} = 3
... {5} = ......

堆栈永远不会比n/2 更深,因为我们首先从{n} 递归到{n-2},当我们需要计算{n-1} 时,我们已经计算了{n-2}

相比之下,在第二个版本中,我们首先从{n} 递归到{n-1},因此堆栈将具有n 级别。

【讨论】:

  • 是的,100% 有意义,所以一个大数字不足以测试它:P
猜你喜欢
  • 1970-01-01
  • 2012-08-15
  • 1970-01-01
  • 2011-12-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-30
  • 1970-01-01
相关资源
最近更新 更多