Lambda演算
在λ演算中,每个表达式都代表一个函数,这个函数有一个参数,并且返回一个值。不论是参数和返回值,也都是一个单参的函数。可以这么说,λ演算中,只有一种“类型”,那就是这种单参函数。
函数是通过λ表达式匿名地定义的,这个表达式说明了此函数将对其参数进行什么操作。例如,“加2”函数f(x)= x + 2可以用lambda演算表示为λx.x + 2 (或者λy.y + 2,参数的取名无关紧要)而f(3)的值可以写作(λx.x + 2) 3。函数的应用(application)是左结合的:f x y =(f x) y。
考虑这么一个函数:它把一个函数作为参数,这个函数将被作用在3上:λf.f 3。如果把这个(用函数作参数的)函数作用于我们先前的“加2”函数上:(λf.f 3)(λx.x+2),则明显地,下述三个表达式:
(λf.f 3)(λx.x+2) 与 (λx.x + 2) 3 与 3 + 2
是等价的。用js代码表达如下,
function f1(f) { return f(3) } function f2() { return function (x) { return x + 2; } } console.log(f1(f2())) //=============或像下面这样写 console.log(f1((function () { return function (x) { return x + 2; } })()));
有两个参数的函数可以通过lambda演算这么表达:一个单一参数的函数的返回值又是一个单一参数的函数(参见Currying)。例如,函数f(x, y) = x - y可以写作λx.λy.x - y。下述三个表达式:
(λx.λy.x - y) 7 2 与 (λy.7 - y) 2 与 7 - 2
也是等价的。js的代码表达如下,
//最普通的实现两个数相加的函数 function add0(x, y) { return x + y; } //使用函数的柯里化实现两个数相加 //也是js的闭包 function add1(num) { return function (numOther) { return num + numOther; } } console.log(add1(3)(2))
然而这种lambda表达式之间的等价性无法找到一个通用的函数来判定。
并非所有的lambda表达式都可以规约至上述那样的确定值,考虑
(λx.x x)(λx.x x)
或
(λx.x x x)(λx.x x x)
然后试图把第一个函数作用在它的参数上。 (λx.x x)被称为ω 组合子,((λx.x x)(λx.x x))被称为Ω,而((λx.x x x) (λx.x x x))被称为Ω2,以此类推。
若仅形式化函数作用的概念而不允许lambda表达式,就得到了组合子逻辑。
插播:关于λ项
其实作为 Haskell 的核心,λ表达式是异常简洁的。下面是关于λ表达式 (λ Expression),也叫λ项 (λ Term) ,的定义:
- 变元是λ项,通常用小写字母写出来,比如 x,y,z 等;
- 如果 M 是λ项,那么 λx.M 也是;
- 如果 M 和 N 都是λ项,那么 M N 也是。
- 变元 (variable) 形态的λ项很简单,就是 x 或者 y 或者 z 或者什么别的,我们通常用单个小写字母,有 时候也加上数字脚标,比如 x0, x1, x2 等,以示区分。
- 抽象 (abstraction) 形态的λ项,写出来就是先用λ这个符号本身作开头,后面跟一个变元,然后一个小点,然后跟一个任意的λ项。例如 λx.x 或 λx.y 或 λx.x x 等。
- 应用 (application) 形态的λ项,就是两个λ项写在一起,中间仅留一个空格做分隔。例如 f x 或者 x x。 写在前面的λ项通常叫函数(function),后面的叫参(argument)。比如在 x x 这个表达式里,就是把 x 这个函数作用于(apply to)它自己。
在实际书写抽象和应用两种λ项时,如果没有一定的标识,往往会产生歧义。所以通常是用括号来把一个λ项和它前后的符号区分开,比如 (λx.x) y 这个表达式,就是说函数 λx.x 作用在参数 y 上。 我们通常不认为括号本身是λ项的一部分,使用它们纯粹是为了书写。
括号的使用有时候也可以略去。约定俗成的习惯是在表达抽象时,小点后面整个式子(除非是遇到反括号) 都是与小点前面的变元对应的λ项。比如 λx.(λy.(x y)) 就可以简写为 λx.λy.x y;在表达应用时则向左结 合,比如 f x y 应该被理解为 (f x) y 而不是 f (x y)。
Lambda演算的语法
Lambda演算只有三类表达式:
- 函数定义:Lambda演算中的函数是一个表达式 ,写成:lambda x . body ,表示“一个参数参数为x的函数,它的返回值为body的计算结果”。 这时我们说:Lambda表达式绑定了参数 x
- 标识符引用(identifier reference):标识符引用就是一个名字,这个名字用于匹配函数表达式中的某个参数名
- 函数应用(function application):函数应用写成把函数值放到它的参数前面的形式,如(lambda x . plus x x) y
Lambda 表达式 自由变量 和 闭合
A particular instance of a variable is “free” in a lambda expression if it is not “bound” by a lambda. For example, x is free in the expressions
x
and
λy. x
but not in this expression
λx. x
Because variables can be repeated, care must be taken to know which variable one is referring to.
For example, in this expression, the underlined occurrences of x are free, the others are not:
λy. x (λx. x x) x
A term is closed if it has no free variables; otherwise it is open
lambda表达式如果没有自由变量,那么其实闭合的。
自由标识符 vs. 绑定标识符
闭包(closure)或者叫完全绑定(complete binding)
在对一个Lambda演算表达式进行求值的时候,不能引用任何未绑定的标识符。
如果一个标识符是一个闭合Lambda表达式的参数,我们则称这个标识符是(被)绑定的;如果一个标识符在任何封闭上下文中都没有绑定,那么它被称为自由变量。
lambda x . plus x y:在这个表达式中,y和plus是自由的,因为他们不是任何闭合的Lambda表达式的参数;而x是绑定的,因为它是函数定义的闭合表达式plus x y的参数。
lambda x y . y x :在这个表达式中x和y都是被绑定的,因为它们都是函数定义中的参数。
lambda y . (lambda x . plus x y):在内层演算lambda x . plus x y中,y和plus是自由的,x是绑定的。在完整表达中,x和y是绑定的:x受内层绑定,而y由剩下的演算绑定。plus仍然是自由的。
我们会经常使用free(x)来表示在表达式x中自由的标识符。
一个Lambda演算表达式只有在其所有变量都是绑定的时候才完全合法。但是,当我们脱开上下文,关注于一个复杂表达式的子表达式时,自由变量是允许存在的——这时候搞清楚子表达式中的哪些变量是自由的就显得非常重要了。
参考:http://cgnail.github.io/academic/lambda-1/
http://a.haskellcn.org/topic/4f9e4037edefd68d37010c8b
http://www.cs.yale.edu/homes/hudak/CS201S08/lambda.pdf
http://www.zhihu.com/question/20349066
http://zh.wikipedia.org/zh-cn/%CE%9B%E6%BC%94%E7%AE%97