可以回顾一下第一章,lambda 演算起源于上世纪 30 年代的数学理论,如今,它是计算机科学理论的重要组成部分。在逻辑上证明及验证系统的辅助工具 (例如,CPU 设计),还是简单形式编程语言,可以准确地解释其他语言的行为。
注意
在下一节,会看到几个用 lambda 演算写的示例“程序”,能以最纯洁和最干净的形式展示很多概念,在本章后面会看到。在 lambda 演算中,整个的“程序”就是一个表达式(2.2.4 节),函数可以将其他函数作为参数(2.3.2 节)。在讨论编程语言之后,我们还要回到这些概念。
我们已经包括了这个背景材料,它能以最纯净的形式说明一些概念。希望你会发现它像我们做的一样有趣,但对于理解本书的其余部分,这并不是必需的。
当 Alonzo Church 在 1932 年引入了 lambda 演算时,他试图把每个数学构造形式化,用最基本的数学概念,函数。写一个数学函数(称为 f),把任意给定参数加上 10,可以写成:
f(x) = x + 10
Church 想在所有的地方都使用函数。事实上,在他的形式化中,一切都是函数。为每个函数起个名字,是不切实际的,因为当一切都写成函数时,许多函数可能只用一次,为此,他引入了一个符号,使写出的函数不需要名字:
(λx.x + 10)
这个表达式表示一个函数,它只有一个参数,用希腊字母 lambda 加变量名(这里是 x)表示;参数声明后面加一个点,再加函数体(这里是 x + 10)。在纯 lambda 演算中,数字(例如 10)、数学运算符(例如 +)都用函数定义,所以,除掉函数,就什么也没有了,这个定义很令人吃惊吧。为使事情简单,我们将使用标准的数字和运算符。继续我们的示例函数,假设我们要把 32 作为参数,结果将是:
(λx.x + 10) 32 = 32 + 10 = 42
为函数提供参数(在 lambda 演算中,称为函数的应用,application),把参数写在函数的后面。当用参数值调用函数时,只是简单地用参数的值(这里 32),替换所有的变量(这里 x)的位置就行了。这就是第一个等号后的表达式。如果我们把 + 看作是内置函数,它将在下一步被调用,产生结果 42。
lambda 演算最有意义的,也是函数编程语言的基石,就是任何函数都可以取函数作为参数。这就是说,我们可以写一个函数,取一个函数(二元运算符)和一个值作为参数,再用这个值作为两个参数,调用这个二元运算符:
(λop.λx.(op x x))
正如你所看到的,我们写的函数,把 op 和 x 作为参数。当函数有多个参数时,用多个 lambda 符号声明更多的参数。在 lambda 函数体中,用 op 来表示函数,用 x 表示给 op 函数提供的第一和第二个参数。如果我们把加法运算符作为第一个参数,21 作为第二个参数,代码可以这样写:
(λop.λx.(op x x)) (+) 21 = (λx.((+) x x)) 21 = (+) 21 21 = 42
有多个参数的函数,函数取第一个参数(这里 op),返回 lambda 表达式,这就成了另一个函数。这样,在第一步,我们应用函数(取 op 作为参数)为参数 (+),它产生的结果在第一个等号的后面,op 变量替换为加号((+))。这个结果仍是带参数的函数,因此,可以继续这个过程。下一步应用函数时,把 21 作为参数 x 的值代入。结果是表达式 (+) 21 21,这是两个数字加法的另一种表示,与 21 + 21 的意思相同,因此,最后的计算结果是 42。正如你所看到的,在 lambda 演算中,计算一直继续,直到没有可计算的函数应用(函数后面有它的参数)为止。
从理论上来看,函数式思想来自 Lambda 演算,但现在我们要把注意力转回现实世界。我们要讨论的第一组概念,是有关在函数程序中的数据表示,这些概念对程序处理数据有重大影响。
2.1 函数编程的基础