0.用Lambda方式描述递归
在很多编程语言中,都可以用Lamda表达式来定义函数,这种函数最大的特点就是没有名字。
函数的Lamda表示法:
λ<name>.<expression>
虽然在大部分关于Lamda calculus 的文献中,函数是被定义成这个样子的,但是λ符号后面的那个name, 并不代表函数的名字,而是指这个函数的参数, 所以Lambda表达式是匿名的函数。在大部分编程语言中,都是通过函数的名字来对函数进行调用的。Lamda本身就是没有名字的,没有名字要如何调用函数呢?
在普通的递归函数中,通过名字来调用函数自身;但在λ演算中,把整个函数的λ表达式写在参数的前面,即表示对函数的调用。如果用和普通非匿名函数同样的方法写匿名的递归函数,当这个函数需要调用自身的时候,就要把自身的表达式完整的写一遍,然而在表达式内又有对自身的调用;怎么办?再写一遍,这样写下去是无穷无尽的。 所以在Lamda函数中调用自身根本就是被禁止的。
这就是这篇东西要解释的问题,到底要如何用λ表达式来写递归函数?
1.不动点
函数f的不动点是一个值x使得f(x) = x。例如,0和1是函数f(x) = x2的不动点,因为02 = 0而12 = 1
2.不动点组合子(Fixed-point combinator,或不动点算子)
对于一阶函数来说,不动点是一个一阶值,高阶函数f的不动点是另一个函数g使得
f(g) = g
那么,不动点算子是任何函数fix使得对于任何函数f都有
f(fix(f)) = fix(f)
这东西有什么用处呢? 把它反过来看
fix(f) = f(fix(f))
进而:
fix(f) = f(f(fix(f))) = ...
很好,看上去像递归了。是的,不动点组合子允许定义匿名的递归函数。它使得用非递归的Lamda方式定义递归函数成为可能。
3.Y组合子(Y-Combinator)
(可能是最简单的)不动点组合子叫做Y组合子。它是Haskell.B.Curry发现的,定义为
Y := λf.(λx.(f (x x)) λx.(f (x x))) 把它应用在另一函数g上
1. Y g = λf.(λx.(f (x x)) λx.(f (x x))) g
2. = λx.(g (x x)) λx.(g (x x))) 对line1, 用g对λf进行β归约得到
3. = g (λx.(g (x x))(λx.(g (x x)) 对line2, 用 λx.(g (x x))对开头的λx进行β归约得到
4. = g (λf.(λx.(f (x x)) λx.(f (x x))) g) 注意看第3行g后面的部分,和第2行完全一样,第2行和第1行等号右边又是一样的
5. = g (Y g)
无类型Lamda中还存在其他的组合子。
Haskell 语言和柯里化都是从Haskell.B.Curry 的名字由来的。
4.用Haskell的Lambda表达式实现1+2+...+n
未完待续...