浅谈λ演算与Python的lambda函数

浅谈λ演算与Python中的lambda函数

作者:牛伯雨

Python里有一个十分方便的功能,那就是lambda函数。使用这一功能,可以让代码显得简洁明快,甚至一行搞定很多步运算。在讨论Python的lambda运算之前,我们先来看看它的原型:λ演算。

λ演算

初识λ演算

λ演算十分简洁,比如λ x . x λx.xλx.x就是一个lambda项(之后我们进一步讨论lambda项的定义)。

形式简单,内容却很丰富:它可以表示一切函数。借助这一工具,我们可以删繁就简探究两个程序的本质是否是一样的,思考编程与逻辑之间的关系,等等。

λ演算是在1936年由阿隆佐·邱奇首次提出的。在同一年,还有另一项深刻地影响着后世的发明——阿兰·图灵的图灵机。这两个发明是分别被独立地提出的,但是不久之后邱奇的学生克莱尼证明,两者在本质上是相同的,真可谓英雄所见略同。也就是说,凡是能用图灵机计算的函数,都可以用λ演算,反之亦然。

图灵机示意图,其作者Schadel已此图它纳入公共领域,可以免费使用

就着图来快速说一下图灵机的基本组成:

  1. 一条无限长的条带,上面有被编号的一个接一个的格子;
  2. 一个读写头,可以在条带上左右移动,也可以写入提前定义好的有限个符号的其中之一,或读取或删除当前格子里的符号;
  3. 一套控制规则,告知图灵机要做什么——写入或删除当前符号,向哪个方向移动读写头,或保持当前状态,亦或进入下一状态;
  4. 一个状态寄存器,以寄存图灵机当前状态。

λ演算的句法结构

两个特征

λ演算具有两个显著的特征:首先,λ演算中的函数都是没有名字的,表达一个函数时,需要把它具体的内容写出来,比如λ x . x + 5 λx.x + 5λx.x+5。这个函数表示,不管你给我一个什么数,我都把这个数加5再输出。

第二点是,λ演算中的一个函数只能接收一个变量。如果我们想写一个接收两个变量的函数,则需要把它写成复合函数的形式,也就是第一个函数的输出结果是紧接着第二个函数的输入变量。
例如,这样一个函数g ( x , y ) = x ∗ x + y ∗ y g(x, y) = x * x + y * yg(x,y)=xx+yy在λ演算中应写为λ x . λ y . x ∗ x + y ∗ y λx.λy.x * x + y * yλx.λy.xx+yy。比如,就这个函数而言,如果x = 5, y = 6,则g ( 5 , 6 ) = 5 ∗ 5 + 6 ∗ 6 = 25 + 36 = 61 g(5, 6) = 5 * 5 + 6 * 6 = 25 + 36 = 61g(5,6)=55+66=25+36=61;同一过程用λ演算,则是
( λ x . ( λ y . ( x ∗ x + y ∗ y ) 6 ) ) 5 (λx.(λy.(x * x + y * y) 6)) 5(λx.(λy.(xx+yy)6))5
= ( λ x . ( x ∗ x + 6 ∗ 6 ) ) 5 = (λx.(x * x + 6 * 6)) 5=(λx.(xx+66))5
= ( λ x . ( x ∗ x + 36 ) ) 5 = (λx.(x * x + 36)) 5=(λx.(xx+36))5
= 5 ∗ 5 + 36 = 5 * 5 + 36=55+36
= 25 + 36 = 25 + 36=25+36
= 61 = 61=61
具体的计算过程是β-归约,小括号则能够把运算过程标识得更清楚。我们稍后详细介绍这两点,这一段的主要目的是说明λ演算中的一个函数只能接收一个变量。

写lambda项的三个(递归的)规则

前面我们说过λ x . x λx.xλx.x就是一个lambda项,在这一节中我们更加详细地介绍lambda项。

  1. 一个单独的变量就是一个有效的lambda变量。比如x。
  2. 如果x是一个变量,t是一个lambda项,那么λ x . t λx.tλx.t也是lambda项。这一法则又叫“lambda抽象”。
  3. 如果t和s都是lambda项,那么ts也是lambda项,这一法则又叫“应用”,就好像把s的值带入t当中。

等价变换lambda项的三个法则

书写出了lambda项后,我们还可以对它进行等价变换,或化简。以下简单介绍化简lambda项的法则。

α-等价

在λ演算中,表示某个变量的符号一般不重要。比如,λ x . x + 5 λx.x+5λx.x+5λ y . y + 5 λy.y+5λy.y+5表示相同的函数,他们是α-等价的。

β-归约

简单来说,β-归约就是把一个数带入到lambda项当中。比如( ( λ x . t ) s ) ((λx.t)s)((λx.t)s),就是把s带入t中的x。
一个具体例子:( ( λ x . ( y + x ) ) s ) ((λx.(y + x)) s)((λx.(y+x))s)即可变换为( y + s ) (y + s)(y+s)
( ( λ x . ( y + x ) ) s ) ((λx.(y + x)) s)((λx.(y+x))s)这个式子中,y是一个“自由变量”。

η-变换

如果当两个函数接收相同的输入时,它们输出相同的结果,那么这两个函数就被认为是等价的,反之也成立。比如说,如果x不在g和k里作为自由变量出现,那么λ x . ( ( g k ) x ) λx.((gk)x)λx.((gk)x)( g k ) (gk)(gk)等价。

λ演算的书写规范

  • 前面我们提到过λ演算中小括号的使用。
    事实上,在λ演算中,应尽量避免使用小括号。但是作为初学者来说,可以写小括号以标明运算步骤。一般来讲,最外层的小括号是没有必要标注的。
  • λ演算的应用过程是左结合的,即 f g x f g xfgx( f g ) x (f g) x(fg)x等价。
  • lambda抽象的范围是从点“.”开始尽可能向右,直到遇上小括号为止。
  • 连续的lambda抽象在写法可以进行缩合,比如λ x . λ y . λ z . f λx.λy.λz.fλx.λy.λz.f可以缩合为λ x y z . f λxyz.fλxyz.f

Python中的lambda函数

Python中的lambda函数句法相当简单:

lambda argument(s) : expression

比如

lambda x: x + 5

就定义了一个加5的函数。
运行

(lambda x: x + 5)(6)

返回的结果为11,
运行

(lambda x, y: 3 * x + 2 * y)(5, 9)

返回的结果为33。

这个基本句法和我们上面介绍的λ演算的句法是连贯的,当然这里面有一些Python语言本身的特征。

在Python中,lambda函数可以帮助我们一行代码完成某项任务,比如将一个数列中的偶数挑出来:

a = [150, 2, 8, 7, 5, 4, 35, 31, 10, 112]
filtered = list(filter(lambda x: x % 2 == 0, a))

运行后,filtered结果为

[150, 2, 8, 4, 10, 112]

lambda函数的另一个好处是,它可以提供一个简洁的“模版”,我们在具体使用的时候可以很简单地对它进行调整。
比如

def lambdaExample(n):
  return lambda a : a * n

myDoubler = lambdaExample(2) # 定义乘以2的函数

myTripler = lambdaExample(3) # 定义乘以3的函数

这样,myDoubler(5)返回的结果就是10,myTripler(5)返回的结果就是15。


版权声明:本文为m0_54633031原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。