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

Haskell中fib_mem与fib_mem_arg的记忆化差异及CAF疑问

Hey there! Let's unpack this Haskell memory puzzle step by step—this is a super common gotcha with lazy evaluation and memoization, so it's great you're digging into it.

Understanding the Difference Between fib_mem and fib_mem_arg

First, Let's Recap the Two Functions

Here's the properly memoized version:

fib_mem :: Int -> Integer
fib_mem = (map fib [0..] !!)
where
  fib 0 = 1
  fib 1 = 1
  fib n = fib_mem (n-2) + fib_mem (n-1)

And the non-memoized imposter that looks similar but doesn't work:

fib_mem_arg :: Int -> Integer
fib_mem_arg x = map fib [0..] !! x
where
  fib 0 = 1
  fib 1 = 1
  fib n = fib_mem_arg (n-2) + fib_mem_arg (n-1)

What Does That Bold Statement Mean?

The line "向函数传入参数会创建新表达式,无法隐式指向之前函数应用的表达式" translates to: "Passing an argument to a function creates a new expression, which can't implicitly reference expressions from previous function applications."

Put simply:
Every time you call fib_mem_arg x, the entire expression map fib [0..] !! x gets rebuilt from scratch—including the infinite list map fib [0..]. Even if you called fib_mem_arg 5 earlier, calling fib_mem_arg 6 won't reuse any of the values you already calculated. Instead, it spins up a brand new list, so all Fibonacci values get recalculated every time they're needed.

A Visual Example with Debug.Trace

Let's add debug logging to see this in action. First, the non-memoized version:

import Debug.Trace

fib_mem_arg :: Int -> Integer
fib_mem_arg x = trace "👉 Creating NEW map fib [0..] list" $ map fib [0..] !! x
where
  fib 0 = trace "  🧮 Calculating fib 0" 1
  fib 1 = trace "  🧮 Calculating fib 1" 1
  fib n = trace ("  🧮 Calculating fib " ++ show n) $ fib_mem_arg (n-2) + fib_mem_arg (n-1)

If you run fib_mem_arg 2, here's what you'll see (abbreviated):

👉 Creating NEW map fib [0..] list
🧮 Calculating fib 0
👉 Creating NEW map fib [0..] list
🧮 Calculating fib 1
👉 Creating NEW map fib [0..] list
🧮 Calculating fib 0
👉 Creating NEW map fib [0..] list
🧮 Calculating fib 1
👉 Creating NEW map fib [0..] list
🧮 Calculating fib 2
... (this repeats exponentially for larger n, leading to massive slowdown)

Now compare that to the memoized fib_mem with trace:

import Debug.Trace

fib_mem :: Int -> Integer
fib_mem = trace "👉 Creating map fib [0..] ONCE" $ (map fib [0..] !!)
where
  fib 0 = trace "  🧮 Calculating fib 0" 1
  fib 1 = trace "  🧮 Calculating fib 1" 1
  fib n = trace ("  🧮 Calculating fib " ++ show n) $ fib_mem (n-2) + fib_mem (n-1)

Running fib_mem 2 gives:

👉 Creating map fib [0..] ONCE
🧮 Calculating fib 0
🧮 Calculating fib 1
🧮 Calculating fib 2

And if you run fib_mem 3 right after, you'll only see:

🧮 Calculating fib 3

No new list creation—we're reusing the same cached list we created the first time!

Why is fib_mem a Constant Applicative Form (CAF)?

A CAF (Constant Applicative Form) is essentially a "constant value" (even if it's a function) that doesn't depend on any external variables. It's defined once, and Haskell's runtime caches it for all future uses.

Look at fib_mem's definition:

fib_mem = (map fib [0..] !!)

There's no parameter here—no x or other variable that the right-hand side depends on. This means map fib [0..] is created once when fib_mem is first referenced, and every call to fib_mem n just indexes into this single, cached infinite list. That's why the memoization works!

In contrast, fib_mem_arg x ties the list creation to the parameter x. Every new x triggers a new list, so no caching happens.


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:51:41