码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2014-10-25 00:47:14      阅读:298      评论:0      收藏:0      [点我收藏+]

标签:style   color   io   os   ar   使用   java   for   sp   

 

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

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

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 组合子:不用 λ演算那一套

标签:style   color   io   os   ar   使用   java   for   sp   

原文地址:http://www.cnblogs.com/billythekid/p/4049574.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!