首页 > 代码库 > λ演算概述

λ演算概述

λ演算(Lambda-calculus)是一套用于研究函数定义、应用和递归的形式系统。它由阿兰佐·丘奇(Alonzo Church)和史蒂芬·科尔·克林(Stephen Cole Kleene)在20世纪三十年代引入。

丘奇运用λ演算在1936年给出“判定性问题”(Entscheidungs problem)的一个否定的答案。关于两个λ演算表达式是否等价的命题无法通过一个通用的算法来解决,这是不可判定性能够证明的头一个问题,甚至还在停机问题之先。

λ演算对函数式编程有巨大的影响,特别是Lisp 语言。

λ演算被称为最小的通用编程语言。λ演算可以用来清晰地定义什么是一个可计算函数。它包括一条变换规则(变量替换)和一条函数定义方式。λ演算之通用在于,任何一个可计算函数都能用这种形式来表达和求值。

因而,λ演算是等价于图灵机的。尽管如此,λ演算强调的是变换规则的运用,而非实现它们的具体机器,可以认为这是一种更接近于软件而非硬件的方式。

语法规则

任何λ表达式都可以通过下述三条BNF范式描述:

范式1、<expr> ::= <identifier>

范式2、<expr> ::= (λ <identifier> . <expr>)

范式3、<expr> ::= (<expr> <expr>)

范式1和范式2描述了“函数定义”,而范式3描述了“函数应用”。

上述BNF范式中的圆括号在不会产生歧义的情况下可以省略。如下假定保证了省略圆括号不会产生歧义:

假定1、函数的应用是左结合的(即从左到右进行计算)

假定2、λ操作符要绑定到它后面的整个表达式

例如, ((λ x. (x x)) (λ y. y)) 可以简写成 (λ x. x x) λ y. y。

在λ演算中,每个表达式都代表一个只有单一参数的函数,这个函数的参数本身也是一个只有单一参数的函数,同时,函数的值是又一个只有单一参数的函数。

函数定义

在λ演算中,函数是通过λ表达式匿名定义的,这个表达式说明了此函数将对其参数进行什么操作。例如:

f (x) = x + 2 在λ演算中表示为λ x. x + 2

因为λ x. x + 2表示了一个函数,所以我们通常称λ x. x + 2是一个函数。

下面的表格是对这个函数的结构的描述:

lambda

parameter

dot

operation

λ

x

.

x + 2

从表格可以看出,表达式λ x. x + 2 定义了一个函数,这个函数对其参数x进行了加二操作。

值得注意的是,如果一个λ表达式定义了一个函数,尽管这个函数的参数没有被赋值,这个表达式也是有值的,这个值就是这个表达式本身。

也就是说,在λ演算中,的概念并不仅仅说一个数字是一个值,一个表达式也可以是一个值。这也是为什么说λ演算对函数式编程语言有有巨大的影响的原因。在函数式编程语言里,函数也可以作为参数传递给其他函数的理念就是来自于此。

函数应用

函数定义表达了函数对其参数进行什么操作。而给函数指定一个实际的参数,将会触发函数对这个参数进行实际的操作,这个过程成为函数应用

以函数 λ x. x + 2 为例,如果给参数 x 指定一个值3,那么将会得到结果5。这个过程称为:“将函数 λ x. x + 2 应用在参数3上”。

而写法上,就如同“范式3”,像下面这样:

(λ x. x + 2) 3

上面在描述函数应用的时候,仅仅讨论了将函数应用在数字上的情况,例如,“将函数 λ x. x + 2 应用在参数3上”这种情况:(λ x. x + 2) 3

如果将函数λ x. x + 2 应用在一个函数(比如 λ y. y + 1)上呢?显然,只要把3 替换成 λ y. y + 1 就可以了:

(λ x. x + 2) (λ y. y + 1)

这得到的仍旧是一个函数,只不过这个函数的参数也是一个函数。下面讨论把这个新得到的函数再应用到数字3上:

(λ x. x + 2) (λ y. y + 1) 3

根据λ演算的两条假定,整个表达式的演算顺序是下面这样的:

1)  (λ y. y + 1)应用到数字3上,得到4

2)  (λ x. x + 2)应用到4上,得到6

注意,虽然λ演算的“假定1”规定函数应用是左结合的,但是“假定2”限制了“λ操作符要绑定到它后面的整个表达式上”。

显然“(λ y. y + 1) 3”是一个整体,所以要先计算出“(λ y. y + 1) 3”的值,再把(λ x. x + 2)应用到计算出的值上。

高阶函数定义和应用

上面对函数的讨论中,只涉及了单一参数函数。

那么,怎样定义一个有多个参数的函数呢?前面讲过,在λ演算体系中,任何函数都只允许有一个参数,有多个参数的函数是不合法的。

例如,f(x, y) = x + y是一个有两个参数的函数,你无法写出一个类似下面这样的λ表达式来表示这个函数:λ x, y. x + y

因为根据“范式1范式2范式3”,这种写法是错误的。但是范式允许你写出如下的表达式:

λ x. λ y. x + y

这条表达式的涵义是:参数为x的函数,对参数x进行了“λ y. x + y”操作。

如果将函数λ x. λ y. x + y应用到数字23上时,写法如下:

(λ x. λ y. x + y) 2 3

这个表达式实际是按照如下顺序进行了一系列计算:

1)  (λ x. λ y. x + y)应用到2上,得到λ y. 2 + y

2)  将得到的λ y. 2 + y应用到3上,得到5

从上面这两条计算过程来看,λ x. λ y. x + y实际应该写为λ x. (λ y. x + y),也就是说,在这个表达式应用到两个参数上的过程中,实际上是先应用到第一个参数上,得到一个中间结果,这个中间结果也是一个函数,再把这个新的函数应用到第二个参数上。

函数的值又是一个函数,在数学上,前一个函数被称为高阶函数。而上面的论述,阐明了λ演算体系中,对高阶函数的定义。

(λ x. λ y. x + y) 2 3这个表达式的计算顺序似乎有违λ表达式范式和假定。“2 3”在符合“范式3”,它理应是一个整体。那为什么不是把“(λ x. λ y. x + y)”直接应用到“2 3”上,而是要先应用到2上,再应用到3上呢?

在我能查到的λ演算相关的文献中,其观点基本都是把23分开不作为一个整体对待的。我是这样认为的,“2 3”虽然在语法上不违背“范式3”,但是它在语义上是不合理的,没有实际意义,所以要分开为两部分。

BNF对语法的表述较为完备,但是对语义的表述则不够好。所以在理解λ演算的过程中,要从表达式的意义上检查其正确性,而不能硬套范式和假定。