为什么 AI、数学顶尖人才都绕不开高阶函数?答案藏在 λ 演算里!

2026-08-20 · Steve Chan

为什么 map、递归、微分方程、神经网络看起来毫不相关,却可以用同一种抽象描述?1936 年丘奇提出的 Lambda 演算只有“变量、抽象、应用”三条规则,却一路连接了 Lisp、函数式编程、Y 组合子、自动微分和今天的 Transformer。再往前一步,不动点又把递归、ODE 求解、PageRank 和神经网络训练串成了一条线:函数吃函数,函数返回函数,再用不动点把无限过程闭合。 这可能才是理解现代 AI 底层计算思想的一把钥匙。

"丘奇 1936 年用 λ 演算证明:所有可计算的东西,都是函数。这句话也适用于深度学习。"

Chapter 0.8 说微分方程的解是"函数返回函数"。再往前迈一步就会发现:当函数能返回函数、函数能吃函数,整个数学和编程世界就被同一种抽象贯穿 —— 这就是 1936 年阿隆佐·丘奇在《一个不可解问题的注记》里给出的 λ 演算。深度学习里堆 100 层的 Transformer、数学里求解微分方程、工程里写一个 map —— 都是同一个抽象的不同投影。这一节用 Clojure 来说,因为 Lisp(1958,McCarthy)是丘奇 λ 演算最直白的化身 —— 句法上几乎一对一。

0.9.1 一切皆函数:λ 演算的三条规则

丘奇 1936 定义 λ 演算时,只有三条原语:变量、抽象(lambda)、应用。就够了 —— 自然数、布尔、数据结构、循环、递归全部能用纯函数搭出来(丘奇编码)。

;; 1. 变量
x

;; 2. 抽象:定义一个函数
(fn [x] (* x x))                       ; λx. x²

;; 3. 应用:把函数喂给一个值
((fn [x] (* x x)) 3)                   ; → 9

写代码的人每天都在用这三件事,但很少意识到:这三条规则足以构造整个数学。这件事和 Chapter 0.5 的母题完全一致 —— 少数几条原语反复折射。

0.9.2 map 是高阶函数,求导也是 —— 它们是同一种东西

中学课本里的"函数"是 \(f(x) = x^2\),吃数、吐数。但只要承认函数也是值,就能让函数吃函数、吐函数 —— 这就是高阶函数:

;; map:经典高阶函数 —— 吃一个 lambda + 一个列表,吐一个新列表
(map (fn [x] (* x x)) [1 2 3 4 5])
;; → (1 4 9 16 25)

;; 求导也是高阶函数:吃 f,吐 f'
(defn D [f]
  (fn [x] (/ (- (f (+ x 1e-6)) (f x)) 1e-6)))   ; 牛顿 1666 的"流数"

(def f       (fn [x] (* x x x)))                 ; f(x) = x³
(def f-prime (D f))                              ; f'(x) ≈ 3x²
(f-prime 2.0)                                    ; → 12.000…

map 和"求导"结构上是同一种东西 —— 都是 (λ → 新东西) 的高阶函数。一旦戴上这副眼镜,数学和编程里到处都是相同的影子:

编程里的高阶函数 数学/物理里的对应
map : (a → b) → [a] → [b] 在每一点逐点应用一个变换(向量场作用)
reduce : (b → a → b) → b → [a] → b 积分:从初值出发把函数值累加(= for 循环)
comp : (b → c) → (a → b) → (a → c) 链式法则 \((f\circ g)' = f'(g)\cdot g'\)
partial : (a → b → c) → a → (b → c) 偏导数:冻住一个变量,剩下的还是函数
D : (R → R) → (R → R) \(\dfrac{d}{dx}\) 算子(Chapter 0.8)
solver : (ODE, y₀) → (t → y) 微分方程的解算子

积分 = reduce。一行 (reduce + y0 (map f ts)) 就是欧拉法的全部 —— 这正是 Chapter 0.8.3 那个 for 循环的 Lisp 写法。

0.9.3 不动点:让 lambda 把自己喂给自己

高阶函数最迷人的玩法是让函数吃自己。若 \(F(x^*) = x^*\),则 \(x^*\) 是 \(F\) 的不动点。这听起来哲学,但它就是递归的数学基础:

;; cos 的不动点 ≈ 0.739085… —— 反复 cos(cos(cos(…))) 会收敛到它
(defn fixpoint [f x0 tol]
  (let [x1 (f x0)]
    (if (< (Math/abs (- x1 x0)) tol)
      x1
      (recur f x1 tol))))                ; Clojure 的 recur = "把答案留给下一个"

(fixpoint #(Math/cos %) 1.0 1e-10)
;; → 0.7390851332151606

有了不动点这副语言,很多看似不相关的事忽然变成同一件事:

现象 它是哪个算子的不动点?
ODE 解 \(y(t)\) 积分算子 \(T[y](t) = y_0 + \int_0^t f(s, y(s))\,ds\)(Picard 迭代)
训练好的神经网络权重 \(W^*\) 一步 SGD 映射 \(W \mapsto W - \eta\nabla L(W)\)(即 \(\nabla L = 0\))
PageRank 排名向量 转移矩阵 \(P\) 的不动点 \(Pv^* = v^*\)(特征向量)
扩散模型去噪终态 反向 SDE 算子的不动点
递归函数 factorial Y 组合子作用在 lambda 上的不动点

最后一行是 λ 演算的高潮 —— Y 组合子。纯 λ 演算里没有 def、没有名字,怎么写递归?丘奇的学生 Curry 找到了答案:让函数把自己喂给自己。

;; Y 组合子:在没有"名字"的世界里实现递归
;; 形式上就是 λf. (λx. f (x x)) (λx. f (x x)) 的 strict 语言变体
(def Y
  (fn [f]
    ((fn [x] (f (fn [v] ((x x) v))))
     (fn [x] (f (fn [v] ((x x) v)))))))

;; 用 Y 实现阶乘 —— fact 自己并不知道自己叫 fact
(def fact
  (Y (fn [self]
       (fn [n]
         (if (zero? n) 1 (* n (self (dec n))))))))

(fact 5)   ; → 120

Y 把"函数返回函数"推到极限:λ (λ (λ ...)) 不断高阶化,最后用不动点把这条无限链闭合。这就是用户问的 "111 lambda lambda lambda" 的精确含义。

0.9.4 神经网络 = lambda 的塔 + 训练 = 找不动点

从这个视角看,深度学习毫不神秘:

  • 每一层 = 一个 lambda:(fn [x] (relu (+ b (matmul W x))))
  • 整个网络 = lambda 的复合:(comp λ_N … λ_2 λ_1) —— 字面意义上的 "111 lambda lambda lambda"
  • 前向传播 = 函数应用
  • 反向传播 = 链式法则在 comp 上的同伴运算(自动微分)
  • 训练 = 在权重空间里找梯度算子的不动点:\(W^* = W^* - \eta\nabla L(W^*)\),即 \(\nabla L(W^*) = 0\)
;; N 层网络 = lambda 的复合,这就是字面意义的 "lambda lambda lambda"
(defn make-net [layers]
  (apply comp (reverse layers)))           ; comp 就是 ∘ 算子

(def net
  (make-net
    [(fn [x] (relu (linear x W1 b1)))      ; λ₁
     (fn [h] (relu (linear h W2 b2)))      ; λ₂
     (fn [h]       (linear h W3 b3))]))    ; λ₃

;; 训练 = 在权重空间里找梯度的不动点
(loop [params init-params]
  (let [grads      (backward (forward params batch))
        new-params (sub params (mul lr grads))]
    (if (converged? params new-params)     ; 不动点判据:∇L ≈ 0
      new-params
      (recur new-params))))                ; recur = "把答案留给下一个"

注意 Clojure 的 recur —— 它就是 Chapter 0.8.3 的"求解留给下一个"在语言层面的关键字化。每轮把当前状态丢给下一轮,直到不动点。ODE 求解、训练神经网络、求 PageRank、Picard 迭代 —— 在 Lisp 眼里是同一段代码。

0.9.5 抽象层级的统一:从底到顶都是同一个 λ

   ┌─────────────────────────────────────────────────┐
   │  扩散模型 / 神经 ODE / RLHF                     │  ← 高级抽象:λ 的塔
   ├─────────────────────────────────────────────────┤
   │  反向传播 = comp 在求导意义下的同伴             │
   ├─────────────────────────────────────────────────┤
   │  每一层 = λ;网络 = (comp λ₁ λ₂ … λₙ)           │
   ├─────────────────────────────────────────────────┤
   │  训练 = 在权重空间里找 ∇L = 0 的不动点          │
   ├─────────────────────────────────────────────────┤
   │  ODE / PDE 解 = 积分算子的不动点(Picard)      │  ← Chapter 0.8 的延续
   ├─────────────────────────────────────────────────┤
   │  积分 = reduce ;求导 = 高阶函数;map ≅ 逐点变换 │
   ├─────────────────────────────────────────────────┤
   │  λ 演算的三条规则:变量、抽象、应用             │  ← 底层抽象(丘奇 1936)
   └─────────────────────────────────────────────────┘

从最底下的三条规则,到最上面的扩散模型 —— 每一层用的都是同一种抽象,区别只在于 λ 叠多深、用哪种不动点逼近。这就是为什么 Lisp 程序员对深度学习有种"早就见过"的感觉 —— 他们 60 年前就在玩 lambda 和 fixpoint。

McCarthy 1958 年发明 Lisp 时直接照搬丘奇 1936 的 λ 演算;今天 PyTorch 的 nn.Sequential 不过是 (comp λ₁ λ₂ … λₙ) 的另一种写法。

0.9.6 可视化与代码

  • 可视化:
  • 不动点迭代的"蛛网图"(cobweb plot):\(\cos\) 不动点、Logistic 映射、Picard 迭代并排
  • Y 组合子的展开动画:Y f → f (Y f) → f (f (Y f)) → …,与神经网络的 (comp f f f …) 同框对照
  • ODE 解 = 积分算子不动点:用 Picard 迭代逐次逼近一条 ODE 的解函数
  • 训练曲线 = 不动点收敛:SGD 轨迹在权重空间向 \(\nabla L = 0\) 收敛的蛛网图
  • 代码:ch00_9_lambda_fixpoint/
  • clojure_lambda_basics.clj —— λ 演算三条规则在 Clojure 里的最小演示
  • map_reduce_as_calculus.clj —— 用 map / reduce 重写数值求导与积分
  • y_combinator.clj —— 没有 def 的世界里用 Y 实现阶乘和斐波那契
  • picard_iteration.py —— ODE 解作为积分算子的不动点(Picard 迭代收敛动画)
  • nn_as_comp_lambdas.py —— 把一个 PyTorch 网络 print 成 (comp λ₁ … λₙ) 的形式
  • training_as_fixpoint.py —— SGD 训练的不动点蛛网图
  • cobweb_plot.py —— \(x_{n+1} = f(x_n)\) 蛛网图通用工具,覆盖上述所有不动点

元教学意义:本章把 0.8 的"返回函数"再推一层。看到深度学习堆 100 层、扩散模型反复去噪、ODE 求解器迭代逼近,统统问一句:"这是哪个算子的不动点?λ 被叠了几层?" —— 答案永远是这两个原语的组合。这就是 1936 年丘奇留给我们的礼物。