上上周就快写完这篇时,IE突然当掉,写的东西烟消云散。俺也元气大伤。这次吸取教训,不用CSDN的在线工具写了。这样的坏处是把文章拷贝到CSDN时,格式难免出错,还得手工调整一下。CSDN什么时候可以实现confluence的在线备份功能呢?还是继续八卦lambda:
自由 vs 有界标识符
标识符和变量其实是一个意思。我记得国内教材里很少用标识符这个说法。不过既然原作者用这个说法,我就跟着用了。上次说到Currying解决了如何处理多参数的问题。在讨论怎么使用lambda前,我们还要解决一个细微但重要的语法问题:封闭(closure),或者叫完全有界(complete bounding)。这里的有界和一阶谓词逻辑里的有界没有本质区别,对一阶谓词逻辑熟悉的老大们可以放心跳过。其实有界涉及的定义很直观,我们看一个例子先。假设我们有一个函数lambda x y. (lambda y. y + 2) + x + y +z,lambda y. y+2里的y和它后面的y是不是一样的呢?显然它们是不一样的。为了处理这种区别,我们引入了有界。当一个lambda表达式被计算时,它不能处理无界的标识符。当一个标识符出现在一个lambda表达式的参数里,并且被包含在这个lambda表达式里,我们就可以说这个标识符有界。如果一个标识符没有被包含在任何一个表达式里,我们就叫它为自由变量。比如说,上面那个lambda表达式里,x 出现在lambda x y .(....)里,所以它是有界的变量,它的包含环境(enclosing context,用“语境”或者“上下文”怎么听怎么别扭,好像俺是《读书》的御用作者似的。:-D)是整个lambda表达式。lambda y. y+2里的y也是有界的,但它的包含环境是lambda y. y+2。标识符z没有出现在包含它的表达式的参数列表里,所以是自由变量。再举几个例子:
- lambda x . plus x y: 这个表达式里,"y"和"plus"都是自由变量,因为它们不是任何包含它们的表达式的参数。x有界,因为它被包含在plus x y里,而plus x y的参数有x。
- lambda x y.y x: 这个表达式里,x和y都有界,因为它们是这个表达式的参数。
- lambda y . (lambda x . plus x y): 在内嵌的表达式lambda x. plus x y里,y和 plus 是自由变量而x是有界变量。在整个lambda表达式里,x和y都有界:x在内嵌表达式界内,而y在整个表达式界内。plus仍然自由。
我们用"free(x)"来代表表达式x里所有自由变量的集合。
一个lambda表达式完全合法仅当它的所有变量都有界。不过当我们考查某个复杂表达式里的子表达式且不考虑上下文时,那些子表达式可以有自由变量-其实确保正确处理那些子表达式里的自由变量非常重要。
Lambda 算子计算规则
其实真正的规则就俩:alpha和beta。Alpha规则又叫转换(conversion)规则,而beta规则又叫简化(reduction)规则。
Alpha转换
这个充满了《星际迷航》味道的规则其实就是重命名操作。它无非是说变量名不重要:给定任何一个lambda表达式,我们可以任意改变参数的名字,只要我们相应地改变这些对应这些参数的变量名字。
比如说,我们有如下表达式:lambda x . if (= x 0) then 1 else x^2
我们通过alpha规则把X改成Y(写作alpha[x/y], 和逻辑里的变量替换一个写法),于是得到:lambda y . if (= y 0) then 1 else y^2
Alpha操作完全不影响lambda表达式的意义。不过我们后面会发现,这个操作很重要,因为它让我们能够实现诸如递归的操作。
Beta简化
Beta简化就有意思了。我们只需要这一个规则,就可以让lamdba算子实现一台计算机能做的任何计算。透过纷繁的表象,我们会发现事情的本质往往出人意料地清晰而简单。删繁为简,恰是数学魅力所在。
Beta规则无非是说,应用一个函数(也就是lambda表达式。一个意思)等价于通过把函数体内有界的变量替换成应用里对应参数的实际值来替换原来的函数。听上去有些拗口(呵呵,其实原文更拗口),但当你看一个例子就知道它其实很简单:
假设我们要应用一个函数:"(lambda x . x + 1) 3
"。Beta规则说,我们可以替换整个表达式,把函数体(也就是“x+1”)里的参数对应的x替换成实际的值3。所以最后的结果是“3+1”。
再来一个稍微复杂点的例子:lambda y . (lambda x . x + y)) q
这个表达式有意思,因为应用了这个表达式后,我们可以得到另外一个表达式。也就是说,它是一个生成表达式的表达式(说到这里,玩儿动态语言的老大们可以笑了,玩儿C/C++/Java的老大们可以流口水了)。当我们对这个表达式应用Beta简化时,我们把所有对应参数y的变量替换成实际的值q。所以结果是"lambda x, x+q"。
再来一个例子:
"(lambda x y. x y) (lambda z . z * z) 3
". 这个带两个参数的函数把第一个参数应用到第二个参数上。当我们计算它的值时,我们把第一个lambda表达式里的变量x换成lambda z. z * z, 再把变量y换成3,得到(lambda z. z * z) 3。对该结果应用Beta简化,我们得到3 * 3。
Beta的严格定义如下:
lambda x . B e = B[x := e] if free(e) /subset free(B[x := e]
这个定义末尾的条件,"if free(e) /subset free(B[x:=e])"道出了我们需要Alpha转换的原因:仅当beta化简不会引起有界变量和自由变量的冲突时,我们可以实施Beta化简。如果一个变量“z”是"e"里的自由变量,那我们得保证beta化简不会让"z"变成有界变量。如果B里的有界变量和”e"里的自由变量重名,我们必须先用Alpha转换,是的重名的变量不再重名。形式化定义不够直观,直观描述又不够简洁。还是来个例子漱漱口:
给定一个表达式,lambda z. lambda x. x+z. 假设我们要应用这个表达式:(lambda z . (lambda x . x + z)) (x + 2)
在实际参数"(x + 2)"里,x是自由变量。但x不是表达式lambda x. x+z的自由变量。也就是说,free(e) /subset free(B[ x:=e])不成立。如果我们打破Beta简化的规则,直接开始Beta简化,便会得到: lambda x . x + x + 2
"x+2"里自由变量,x,现在变得有界了。如果我们把结果应用到3上:(lambda x. x+2+2) 3,我们得到3 + 3 + 2。
如果我们按正常程序办事呢?
应用 alpha[x/y]: lambda z . (lambda y . y+z)) (x + 2)
应用 beta: lambda y . y + x + 2) 3
再次应用beta: 3 + x + 2
.
"3+x+2" 和 "3+3+2" 很不一样哈!
规则就这些了。我们还可以选择性地加一个所谓的Eta-化简,不过它不是必需的。我们就此跳过。我们讨论的这套系统已经是图灵完备的计算体系。那这套系统到底有什么用嗫?到底怎样才能让这套系统变得真正有用嗫?嗯,要说明这些问题,我们得先定义一些基本的函数,以便我们做算术,条件测试,递归,等等。这些会在以后的帖子里谈到。
我们也还没有谈到适合lambda算子的模型(Good Math Bad Math的作者在这里和这里讨论了模型)。模型也是很重要的东西。逻辑学家用了好几年时间研究lambda算子,才搞出一个完备的模型。而且早先时候,尽管lambda算子看起来没错,为它制订模型的工作却失败了。这在当时极为引人关注。要知道,毕竟一个系统没有有效的模型就没有实际的意义。