Lambda演算是FP语言的核心,出于想深入的了解FP语言,于是开始啃《The Implementation of Functional Programming Languages》,希望可以坚持下去。Lambda演算在我理解,就是一系列的运算法则,就比如,在数学里面,向量的某些运算法则一样。
就举书上的例子:(+ 4 5)就是一个简单的Lambda表达式。Lambda表达式都是写出前缀的形式,且括号同样表示着高优先级。函数语言程序的实现总是被认为是一个表达式,通过计算出其值来执行他。
这里介绍一些内置函数:
在C语言中,我们常常会有多参数函数,比如int sum(a,b)表示a+b的一个函数。我们可以把+当做是一个有两个参数的函数,比如(+ 4 5)就表示把函数+应用到4和5上,结果就是9。在Lambda演算中,每个函数只有一个参数,那么,该如何表示“+”这个函数呢?
其实(+ 4 5)可以改写成((+ 4) 5),则对于“+”而言,他是一个函数,其参数是一个数字,返回一个函数(+ 4),这个函数对于所有被应用的参数,都会加上4。这种形式的表示方法叫做Curring。
个人理解,Lambda Abstractions其实就是一个lambda函数。而这个函数应用到某个参数上,则叫做Lambda Reduction。
对于(+ 4)这个函数,其Lambda Abstraction为:(λx . + x 3)。这里λ表示这个是一个Lambda Abstraction,x表示其参数,后面的则是表达式,即函数体。一个函数体中也可能包含另一个Lambda Abstraction,比如:(λx. (λy. – y x)),则表明了函数“-”。
考虑LE(Lambda Expression):(λx. + x y) 4,为了能够计算出这个表达式的值,我们需要知道全局变量y的值是多少,而x的值是不用知道的,因为x的值由参数给出,这里就是4。现在我们可以发现,x和y在表达式(+ x y)中,是两个不同类型的变量,我们称x在表达式(+ x y)中被λx绑定(Bound),且y在表达式(+ x y)中是自由的(Free)。通常来说,一个表达式的值只依赖于其中的Free变量。
Beta-conversion描述了如何把一个Lambda Abstraction应用到一个变量上。其实操作就似乎把LA中的被绑定变量用参数代替,比如:(λx. + x 1) 4 –> + 4 1就应用了beta-conversion。
对于函数体中含有别的LA的函数,其替换方法和前面类似:也是把其中的被绑定变量用参数代,比如 (λx. (λy. – y x)) 5 4 –> (λy. – y 5) 4。这里我们发现,其实函数(λx. (λy. – y x)) 传入变量5之后会返回另一个函数(λy. – y 5),这就是Curring。
注意,这里的替换只是替换被绑定的变量!如(λx . (λx . + ( – x 1)) x 3 ) 9在替换最外层的参数9的时候,只会替换后面出现的x。因为只有这个x是被第一个λx绑定的。可以这样思考,把内层的(λx . + ( – x 1)) 改写成(λy . + (- y 1)) 【这其实是一个alpha-conversion】。
通常(λx. (λy E))会被缩写成(λx . λy . E)。
在前面我们介绍了CONS、HEAD和TAIL。这三个函数其实也可以用纯粹的Lambda Abstraction来表示:
由Lambda Abstraction应用于参数之后的操作叫做β-reduction,而反过来的操作叫做β-abstraction,β-conversion则表示一个双向的操作。β-reduction和β-abstraction表示两种操作,此外,β-conversion可以表示两个式子之间的相等关系。
考虑两个函数(λx . + 1 x)和(λy . + 1 y),这两个函数的结果其实是一样的。由函数1转换到函数2的过程叫做α-conversion。值得注意的是,并不是两个函数结果一样,就表示这两个是α-conversion得到的,比如(λx . + 1 x)和(λy . + y 1),虽然结果是等价的,但是并不是α-conversion。
α-conversion的步骤通俗的说就是,把函数中bound的变量,替换为另外一个不与函数体中别的变量冲突的名字,同时把参数名改为新的名字。
有的时候α-conversion会被用来解决命名冲突的问题,比如:有函数TWICE = (λf . λx . f (f x)),现在来推导表达式(TWICE TWICE)。
TWICE TWICE = (λf . λx . f (f x)) TWICE → (λx . TWICE (TWICE x)) 【β-conversion】= (λx . TWICE ((λf . λx . f (f x)) x)),对于下划线的部分其中的x是被第二个λx绑定的,而最后一个x是被第一个λx绑定的,如果我们进行如下的转换:(λx . TWICE ((λf . λx . f (f x)) x)) → (λx . TWICE ((λx . x (x x))),其实表达式x (x x)中的x含义是不一样的,究竟哪一个被第一个λx绑定,哪一个被第二个λx绑定是不确定的。这个被称为Name-capture Problem,常常用α-conversion来解决,即把下划线部分转换为(λy . f (f y))即可以。
考虑两个LA,(λx . + 1)和(+ 1)。这两个LA作用于同一个变量的时候,其求值过程是一致的。其实这两个LA是可以通过η-conversion得到的。通常来说:(λx . F x) ↔F,若x在F中不是free的,且F是一个函数。
δ-conversion常常用来表示内置函数的转换,比如IF等。
符号E[M/x]表示在E中用M代替自由的x,对于β-conversion,则可以简单的写成:(\lambda x . E) M ↔β E[M/x]
如果一个表达式不再含有可以规约的项,我们则称这个表达式已经计算结束,且这个最终形式被称为normal form。关于normal form,有如下几个注意点:
不同的规约方法可能得到不同的normal form么?关于这个问题,Church-Rosser Theorem I 给出了答案:不会有不同的normal form。
Normal order reduction是指,每次都处理最左边的redex。Church-Rosser Theorem II则指明,若E1→E2且E2是normal form,那么必定存在一个从E1到E2的normal order reduction序列。
但是并不是说Normal order reduction是最优的方式。
Lambda Calculus是可以表示递归函数的,但是Lambda Calculus并没有给函数命名,该怎么表达呢?其实说白了,就是想办法使得递归转为非递归。考虑下面的函数:FAC = (λn . IF (= n 0) 1 (* n (FAC (- n 1))))如果使用β-abstraction则得到式子FAC = (λfac . (λn . IF (= n 0) 1 (* n (fac (- n 1))))) FAC,若把(λfac . (λn . IF (= n 0) 1 (* n (fac (- n 1))))) 记为H,则可转换为FAC = H FAC,这个其实就有点像解方程,最终把FAC给解出来。其实对于f( x ) = x,我们则把x称为函数f的不动点。对于Lambda Calculus也是这样,这里FAC是函数H的不动点。
那么如何解除FAC呢?这里建立一个函数Y,使得Y H是H的不动点,即Y H = H (Y H),这里Y称为不动点组合子。从而可得FAC = Y H,从而在函数体中去除了FAC。
那么Y到底是什么呢?不知道会不会影响计算呢?大家可以动手算算就知道了。其实Y = (λh . (λx . h (x x)) (λx . h (x x)))。