又是 lambda (WIP)
时隔良久,又忙里偷闲捡起来这东西。
看了这个博客,感觉是很好的补充,又把没理解的东西捡起来试图理解一下。(也是在翻了这作者的博文之后,让我起了好好记录 blog 的念头。
解方程
对于阶乘函数 fact
fact :: Int -> Int
fact n = case n of 
  1 -> 1
  n -> n * fact (n-1)
有一个问题是如何只用 lambda 演算来实现 fact。具体一点来说,需要在 fact 内部不能调用 fact  自身。
一定程度上来说,这个要求是很直观的,因为在函数内部调用 fact 时,fact 自身还没定义完,怎么能够调用它呢?
在上面提到的博文当中,该文作者视函数定义为一个方程(可能也确实是),fact n 后的等号 =,就是数学意义上的等号,等号两侧完全等价。
如果能够利用这个方程,直接解出 fact 的定义,那么也就得到了 fact 的定义,绕过了先有鸡还是先有蛋的问题。
不动点
中学数学里其实很直接少见到递归的概念,偶尔出现也只是出于数列题目,浮光掠影。
然而哪怕只是很少出现,也有人已经把不动点的概念引入到各种高中教辅书里,配上标题:『不动点法求通项公式』,配图往往有两种,一种是一个螺旋状,另一种是折线。(折线的大概长这样):
       ▲
       │
       │
       │
       │
       │                                                 xx
       │                                                xx               lll
       │                                              xx         lllllllll
       │                                           xxx     llllll
       │                                        xxx lllllll
       │                                     llllllll
       │                               lll▲─►l
       │                         lllll┌───┘x
       │                     lllll    ▲xxx
       │                 lll┌────────►┘x
       │             lllll  ▲      xxx
       │         lllll      │    xx
       │     lllll          │  xx
       lllllll▲─────────────►xx
lllllll│      │            xx
l      │      │         xxx
       │      │       xxx
       │      │     xxx
       │      │    xx         y=x
       │      │  xx
       │      │xx
       │     x│x
       │    xx│
       │  xx  │
───────┘xx────┴───────────────────────────────────────────────────────────────►
      xx
    xx │
   x   │
 xx    │
基本意思就是不动点随着数列趋于极限,将逐步收敛到一个“稳定”的定值。或者干脆就等于这个值,同时因为这个点很“稳定”,于是它不再往外跑,从而得到了一个解。
那时候的理解很粗糙,没能真正弄懂极限与无穷。但是从利用这个工具解题的过程中还是获得了一点认知:谁掌握了不动点,谁就掌握了整个复杂过程的解。
方程例子
上面提到的 blog 里,作者引用的是函数定义上的不动点,即若 f(x)=x,则 x 是 f 的一个不动点。但我觉得数列的例子可能更像一个算法,于是多写了几笔。
言归正传。先从阶乘的例子来看 组合子怎么用。 组合子有这样的性质:
Y f = f Y f
也就是说 组合子是这样一个高阶函数:对于任意函数 , 都是 的一个不动点
f (Y f) = (Y f) ; 函数作用是右结合的
定义对于阶乘函数 fact,定义一个 mk-fact:
(define (mk-fact fact')
  (lambda (n)
    (cond
      ((= n 1) 1)
      (else (* n (fact' (sub1 n)))))))
我擦,看起来好麻烦,我换个随意点的写法:
mk-fact fact' =
  if n = 1  => 1
  else      => n * (fact' (n-1)) ; 意思到了就行
然后直接将 mk-fact 给  组合子:
Y mk-fact = mk-fact (Y mk-fact)
接着传递 n 的时候,神奇的事情发生了:
(Y mk-fact) n 
->
(mk-fact (Y mk-fact)) n
->
if n = 1  => 1
else      => n * ((Y mk-fact) (n-1))
如果记 fact = Y mk-fact,则:
fact n =
  if n = 1  => 1
  else      => n * (fact (n-1))
就此得到了阶乘函数 fact 家人们
Y-combinator
这就是 组合子
随便带入一个 :
在 apply 一个具体 f 之后, 组合子变成了类似  组合子  的东西
此式永远能继续规约,不能规约到一个 normal form,因而说它是发散的(diverge)
如果 作用于自身,直接代入即有
回过头来
我们总结一下寻找 fact 的过程:
首先,先把 fact 写出来
fact = lambda n.
  if n = 1 => 1
  else => n * (fact (n-1))
抽象一下,就是写一个函数体里有 fact 递归调用的函数
fact = ... fact ...     (1)
然后我们假设 fact 是某个函数 g 的不动点,即 g fact = fact
于是 fact = g fact          (2)
于是 g fact = ... fact ...  (3),将 (2) 代回 (1) 即得
现在又已知 Y 组合子能求出任意函数的不动点,Y g = g Y g  (4)
于是设 Y g = h,则 g h = h          (5),带入 (4) 即得
于是 g h = ... h ...    (6),将 h 作为 fact 代入 (3) 即得
于是 h = ... h ...    将 (5) 代回 (6)
于是解出了阶乘函数(递归函数)h
简直是魔术