partial functions & total functions
(looking a lat)
表示在 lat 中找 a,寻找方式是,先看第一个元素,如果是数字(比如 i)就跳到第 i 个位置,重复操作,直到找到非数字。判断这个非数字元素是否是 a。
(define keep-looking
(lambda (a sorn lat)
(cond
((number? sorn) (keep-looking a (pick sorn lat) lat))
(else (eq? sorn a)))))
(define looking
(lambda (a lat)
(keep-looking a (pick 1 lat) lat)))
keep-looking
函数的特点是它并不是对列表 lat 进行调用,所以被称作 unnatural recursion。
但是,keep-looking 有可能不会停机,即它只能接受部分合法的参数,所以被称为 partial functions,前面提到的可以接受所有参数的函数被称为 total functions。
(shift pair)
表示让 pair(这个 pair 的第一个元素也是一个 pair)的第一部分的第二个元素和其第二部分形成一个 pair,如(shift '((a b) (c d))) => '(a (b (c d)))
(define shift
(lambda (pair)
(build (first (first pair))
(build (second (first pair))
(second pair)))))
再考虑一下
(define align
(lambda (pora)
(cond
((atom? pora) pora)
((a-pair? (first pora))
(align (shift pora)))
(else (build (first pora)
(align (second pora)))))))
这个函数递归的参数并不是原来参数的子序列,可能不停机。
(参见)Commandment 7。
所以我们定义一个 length* 函数计算其复杂度
(length* pora)
计算 pora 中 atom 的数量
(define length*
(lambda (pora)
(cond
((atom? pora) 1)
(else(+ (length* (first pora))
(length* (second pora)))))))
然而实际上 align 函数的每次递归都会简化 pora 的第一个参数,因此这个 length* 参数不能准确判断“参数是否简化”,所以不妨给第一个元素设置一个更高的权重
(define weight*
(lambda (pora)
(cond
((atom? pora) 1)
(else (+ (* 2 (first (weight* pora))))
(weight* (second pora))))))
这个时候发现 align 函数每次会使得 weight* 不断减小,因此 align 最后会停机
其实 align 是一个把 list 内元素向右对齐的函数,每次只要头部的元素减少,那么就更加接近目标,所以其复杂度也是减小的。
这里提出了一种判断函数是否停机的方法:设置一个价值计算函数,如果价值计算函数的结果不断缩小,则函数最终会停机。
(define shuffle
(lambda (pora)
(cond
((atom? pora) pora)
((a-pair? (first pora))
(shuffle (revpair pora)))
(else (build (first pora)
(shuffle (second pora)))))))
这个 shuffle 函数是将 align 函数中的 shift 换成 revpair,但是它不会停机(比如输入 '((a b) (cd))
) ,因此它是 partial function
(eternity x)
一个不会停机的函数
(define eternity
(lambda (x)
(eternity x)))
(C n)
计算 Collatz 猜想(即 3n+1 问题)
(define C
(lambda (n)
(cond
((one? n) 1)
((even? n) (C (quotient n 2)))
(else (C (add1 (* n 3)))))))
Thanks to Lothar Collatz
(A n m)
计算阿克曼函数
(define A
(lambda (n m)
(cond
((zero? n) (add1 m))
((zero? m) (A (sub1 n) 1))
(else (A (sub1 n)
(A n (sub1 m)))))))
这个函数计算量非常大,即使是 (A 4 3)
也算不出来
Thanks to Wilhelm Ackermann
停机问题
停机问题,即是否存在一个函数在有限时间内判断另一个函数会不会停机。
首先假设存在这样的函数 (will-stop? f)
,显然这必须是一个 total function,返回值为 \#f
或者 \#t
下面再定义另外一个函数 (last-try x)
(define last-try
(lambda (x)
(and (will-stop? last-try)
(eternity x)))) ;; eternity 是一个不会停机的函数
然后运行 (will-stop? last-try)
如果 (will-stop? last-try)
为 \#f
,此时得到了 last-try 的值为 \#f
,也就是说 last-try 停机了,所以 (will-stop? last-try)
的值为 \#t
,矛盾。同理,如果假设为 \#t
仍然得到矛盾。
因此不存在 (will-stop? f)
这样的函数。
Thanks to Alan M. Turing Thanks to Kurt Gödel
Applicative-order Y combinator
- 首先回顾 length 函数:
(define length
(lambda (l)
(cond
((null? l) 0)
(else (add1 (length (cdr l)))))))
- 现在我们不能用
define
定义递归,但是我们还是想实现递归。假设有一个现成的self
函数表示“当前定义的函数”,那么就可以直接用self
进行定义了:
(lambda (l)
(cond
((null? l) 0)
(else (add1 (<self> (cdr l))))))
- 但是这个
self
究竟是什么?我们还不知道,既然不能用<self>
,那么不妨将self
参数化:
(lambda (self)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (self (cdr l)))))))
- 为了方便,不妨给这段程序定义一个名字(这里虽然用了
define
,但是没用它定义递归):
(define length
(lambda (self)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (self (cdr l))))))))
我们想要让程序实现递归,那么不妨在调用时传入自身,即 ((length length) 3)
(这里已经定义了 length
,所以可以直接用)。
事实上,此时没有用到 define
,而我们现在已经实现了递归调用!
- 我们可以进一步把
((length length) x)
这个过程用高阶函数实现:
((lambda (length)
(length length))
(lambda (self)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (self (cdr l))))))))
- 但是这个函数还有一个问题,
self
为(lambda (self) (lambda (l) ...))
,所以self
调用的第一个参数应该是self
:
((lambda (length)
(length length))
(lambda (self)
(lambda (l)
(cond
((null? l) 0)
(else (add1 ((self self) (cdr l))))))))
- 上面的程序中,
(lambda (l) ...)
和我们一开始的定义差不多,唯一的区别在于<self>
变成了(self self)
。不妨和上面一步一样,将这里的(self self)
再次进行提取出来:
((lambda (length)
(length length))
(lambda (self)
((lambda (self-self)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (self-self (cdr l)))))))
(self self))))
- 但是这段代码有一个问题:
(self self)
这一步没法停机,会不断求值。因此我们需要用lambda
进行包裹,以实现延迟计算:
((lambda (length)
(length length))
(lambda (self)
((lambda (f)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (f (cdr l)))))))
(lambda (x) ((self self) x)))))
此时中间的 (lambda (l) ...)
那一部分就和我们一开始的定义一模一样!并且仅仅靠外面这一层就实现了递归:
((lambda (length)
(length length))
(lambda (self)
((lambda (f) ...)
(lambda (x) ((self self) x)))))
- 最后,我们将外面这层包装抽象出来:
((lambda (le)
((lambda (length)
(length length))
(lambda (self)
(le (lambda (x) ((self self) x))))))
(lambda (f)
(lambda (l)
(cond
((null? l) 0)
(else (add1 (f (cdr l))))))))
- 我们就得到了 Applicative-order Y combinator:
(define Y
(lambda (le)
((lambda (f) (f f))
(lambda (f)
(le (lambda (x) ((f f) x)))))))