首页 > 代码库 > 重新发明 Y 组合子:不用 λ演算那一套

重新发明 Y 组合子:不用 λ演算那一套

 

重新发明 Y 组合子:不用 λ演算那一套

<style></style>

重新发明 Y 组合子:不用 λ演算那一套

目录

  • 1. 一类奇妙的函数
    • 1.1. 三个例子
    • 1.2. 构造方法
    • 1.3. 程序分解
  • 2. X 组合子
  • 3. Y 组合子
  • 4. 结语

1 一类奇妙的函数

 

1.1 三个例子

函数

(lambda ($)  (lambda (n)    (if (< n 2)        1        (* n (($ $) (- n 1))))))

以一个函数为参数,当这个参数是函数本身时,返回计算阶乘的函数

函数

(lambda ($)  (lambda (l)    (if (null? l)        0        (+ 1 (($ $) (cdr l))))))

以一个函数为参数,当这个参数是函数本身时,返回计算列表长度的函数

函数

(lambda ($)  (lambda (n)    (cond ((= n 0) 0)          ((= n 1) 1)          (else (+ (($ $) (- n 1))                   (($ $) (- n 2)))))))

以一个函数为参数,当这个参数是函数本身时,返回计算 Fibonacci 数的函数

1.2 构造方法

上面三个函数的构造方法很机械。

以第一个为例。

常见的计算阶乘的函数

(define fact  (lambda (n)    (if (< n 2)        1        (* n (fact (- n 1))))))

该函数的名字是

fact

该函数的函数体是

(lambda (n)  (if (< n 2)      1      (* n (fact (- n 1)))))

该函数的名字在该函数的函数体中的上下文是

(lambda (~)  (lambda (n)    (if (< n 2)        1        (* n (~ (- n 1))))))

以一个函数为参数,当参数是自身时返回计算阶乘函数的函数就是

(lambda ($)  (lambda (n)    (if (< n 2)        1        (* n (($ $) (- n 1))))))

1.3 程序分解

重复上一节的最后两个步骤。 我们是从【fact 函数的名字在 fact 函数的函数体中的上下文】

A = (lambda (~)      (lambda (n)        (if (< n 2)            1            (* n (~ (- n 1))))))

构造【以一个函数为参数,当参数是自身时返回计算阶乘函数的函数】

B = (lambda ($)      (lambda (n)        (if (< n 2)            1            (* n (($ $) (- n 1))))))

的。但是正如你看到的那是无理由的机械构造。

那么问题来了。B 真的能由 A 构造吗?

对比一下

;; 只有一点点不同A = (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~     (- n 1)))))) ;; 一个是 ~B = (lambda ($) (lambda (n) (if (< n 2) 1 (* n (($ $) (- n 1)))))) ;; 一个是 ($ $)

很像数学

   a    * c (a + b) * c = a * c + b * c

式子

(a + b) * c

确实可以由式子

a * c

来构造。

这是乘法的【结合律】。

程序也有【结合律】吗?

数学中乘法的【结合律】是用【因式分解】证明的。

程序里面怎么做【因式分解】?

用【上下文】。

分解谁?

分解

B = (lambda ($) (lambda (n) (if (< n 2) 1 (* n (($ $) (- n 1))))))

因为,我们想看看它是不是由

A = (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))

构造的。

怎么分解?

($ $)

的上下文,因为它是两者唯一的不同点。

怎么取它的上下文呀?包含它的表达式起码有

1. ($ $)2. (($ $) (- n 1))3. (if (< n 2) 1 (* n (($ $) (- n 1))))4. (lambda (n) (if (< n 2) 1 (* n (($ $) (- n 1)))))5. (lambda ($) (lambda (n) (if (< n 2) 1 (* n (($ $) (- n 1))))))

5 个。取它在哪个表达式中的上下文呀?

都取试试看

1. (lambda (~) ~)2. (lambda (~) (~ (- n 1)))3. (lambda (~) (if (< n 2) 1 (* n (~ (- n 1)))))4. (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))5. (lambda (~) (lambda ($) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1)))))))

1 是废话,不要。

2 和 3 少了n 的信息,也不要。

5 多了无用的 dollar ,也不要。

4 提供的绑定不多不少,就要它了。

而且回过头来看 4 正好就是 A

4.  (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))A = (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))

现在更加确定 A 能构造出 B ,即 B 能分解出 A 来。

把 4 装回 B 中去。【装逼去!】

A = (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))B = (lambda ($) (lambda (n) (if (< n 2) 1 (* n (($ $) (- n 1))))))4.  (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))));; 装回去B = (lambda ($) ((lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))                 ($ $)))

这样装 B 对不对呀?

不对。

在真 B 中 (dollar dollar) 在求值进入到里面那个函数之后,如果 n 小于 2 就不用求值,如果 n 不小于 2 才求值。

而装 B 中 (dollar dollar) 不用等到求值进入到里面的那个函数,求值才刚进入外面这个函数就必须求值了。

所以说装 B 不对。

怎样把装 B 修复成真 B ?

装 B 把真 B 中的一个求值提前了,那我们就把它延迟一下就行了。

B = (lambda ($) ((lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))                  (lambda (n) (($ $) n))))

现在 B 中分解出 A 来了,证实了能从 A 构造出 B 来,我们的程序分解结束了吗?

没有,A 插入 B 中太深,拔出一点点来才好。

B = ((lambda (^)       (lambda ($)          (^ (lambda (n) (($ $) n)))))      (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1)))))))A =   (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1))))))

这种深度最舒服了。

2 X 组合子

把剩下的两个例子也分解一下和上一节的放在一起

((lambda (^) (lambda ($) (^ (lambda (n) (($ $) n))))) (lambda (~) (lambda (n) (if (< n 2) 1 (* n (~ (- n 1)))))))((lambda (^) (lambda ($) (^ (lambda (list) (($ $) list))))) (lambda (~) (lambda (list) (if (null? list) 0 (+ 1 (~ (cdr list)))))))((lambda (^) (lambda ($) (^ (lambda (n) (($ $) n))))) (lambda (~) (lambda (n) (cond ((= n 0) 0) ((= n 1) 1) (+ (~ (- n 1)) (~ (- n 2)))))))

三个函数的第一行都是一样的,抽象出来吧

(lambda (^)  (lambda ($)    (^ (lambda a (apply ($ $) a)))))

取个什么名字好?

X 呀,因为之前又是 B 又是 插入太深的。

(define X (lambda (^) (lambda ($) (^ (lambda a (apply ($ $) a))))))

现在有了 X 了,用一句话说清楚 X 有什么用?

X 能将【有名字的递归函数的名字在函数体中的上下文】转换成【以一个函数为参数,当参数是它本身时,返回递归函数的函数】。

X!这么复杂的表述方式,但是老子还是听懂了。那有没有
能将【有名字的递归函数的名字在函数体中的上下文】转换成【递归函数】
的函数。

有,就是下一个字母 Y 。

3 Y 组合子

(define Y  (lambda (^)    ((X ^) (X ^))))

4 结语

老子总算懂 Y 组合子了。但这个过程中经历一次装B失败。

其实如果仔细研究最简单的

(define fact  (lambda (n)    (if (< n 2)        1        (* n (fact (- n 1))))))

这种类型的递归函数,就可能避免这次装B失败。

【递归函数总要使用 if 或者 cond 】

因为它们特殊的求值规则,能够延迟或者取消对递归部分的求值,避免无穷递归下去。

重新发明 Y 组合子:不用 λ演算那一套