You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Haskell的thunk如此高效?斐波那契线性时间之谜

Haskell线性时间斐波那契的原因

你的猜想完全正确:Haskell里的惰性求值结合纯函数的不可变性,实现了计算结果的自动缓存(也就是记忆化),避免了重复计算,因此能达到线性时间复杂度。

拆解Haskell的实现逻辑

fib = 0 : 1 : zipWith (+) fib (tail fib)

这里的fib是一个单一的惰性列表,所有对fib的引用都指向同一个列表实例:

  • 当程序需要某个位置的元素时,才会逐步求值前面的元素;
  • 每个元素被计算一次后,就会被永久缓存(因为纯函数保证值不会被修改);
  • zipWith计算新元素时,直接复用fib和tail fib中已经求值过的结果——tail fib本质是fib去掉第一个元素的共享结构,不需要重新生成整个序列。
    整个过程等价于从0、1开始,每一步只做一次加法推导下一个元素,总时间复杂度为O(n)。

你的Ruby/Python实现的问题

你写的版本里,每次在生成器内部调用fib()都会创建全新的独立生成器实例,这些实例之间没有共享任何计算状态:

  • 计算第3个元素时,lhs = fib()和rhs = fib()都会从头生成0、1;
  • 计算后续元素时,每个新值的生成都会触发多个生成器从头遍历、重复计算前面的所有元素,最终导致时间复杂度变成指数级O(2ⁿ)。

修正后的线性时间版本

Ruby版本

核心是共享同一个生成器的状态,避免重复创建实例:

def fib
  Enumerator.new do |yielder|
    a, b = 0, 1
    loop do
      yielder.yield a
      a, b = b, a + b
    end
  end
end

gen = fib
20.times { puts gen.next }

Python版本

同样通过复用状态实现线性计算:

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

gen = fib()
for _ in range(20):
    print(next(gen))

关键概念梳理

  • 惰性求值≠自动优化:Ruby/Python的生成器是惰性的,但没有纯函数语言的不可变性保障,无法自动缓存结果;
  • 共享不可变结构:Haskell的列表是不可变的,fib和tail fib共享大部分序列结构,避免了重复生成;
  • 记忆化:纯函数特性让Haskell可以安全地缓存所有求值后的结果,所有引用同一表达式的地方都会复用缓存值。

内容的提问来源于stack exchange,提问作者itarato

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 08:27:25