lambda calculus入门

lambda算子是一切函数式语言的基础,明白lambda算子对于掌握函数式语言有着许多好处。最近学习相关知识略有所得,故在此写出以备忘:

1 基础

λ 算子是函数式编程的理论基础,是图灵机外的另一种计算模型。 它十分简洁,只有三条产生规则,却可以表达一切可计算的函数。

λ 算子的核心概念是表达式expression。λ 算子的产生规则如下:

<expression> ::= <name>|<function>|<application>
<function> ::= λ <name>.<expression>
<application> ::= <expression><expression>

需注意:

  • 其中的<name>代表标识符。
  • 表达式默认左结合(即从最左开始起作用),如果需要改变顺序可以加括号。

1.1 function

function表示函数的定义,采用

λ <name>.<expression>

的形式。 <name>代表函数的参数,expression则是该函数的返回值。

λ 中的function都是单参数函数,如下例:

λ a.a+1

本例中假设已定义+操作

那么如何定义多参数函数呢?可以使用嵌套结构:

λ a.λ b.a+b

这个例子也可以简写为

λ a b.a+b

1.2 application

application表示函数的应用。应用中,参数的使用采用替换的方式来完成, 因此函数中参数的名字实际是无关紧要的,所以

λ x.x ≡ λ y.y ≡ λ z.z

用以下符号来表示替换:

(λ x.x)y = [y/x]x = y

[y/x]表示把x替换成y, 一个简单的记法是把x想成分母,乘了后面的x以后就只剩下y了,这样就完成了替换

1.3 自由变量和约束变量

λ 算子中,我们把出现在λ 后参数中的变量称为约束变量, 把不出现的λ 后的变量称为自由变量,比如:

λ x.xy

x为约束变量,y为自由变量。

自由变量的判断规则有下列三条:

  • 在<name>所对应的部分<name>是自由的
  • 在λ<name1>.<exp>中,<name>是自由的当且仅当<name>≠<name1>且<name>在exp中自由
  • 在E1 E2 中,<name>是自由的当且仅当<name>在E1 中自由或<name>在E2 中自由

约束变量的判断规则有下列两条

  • 在λ<name1>.<exp>中,<name>是约束的当且仅当<name>=<name1>或<name>在exp中是约束的
  • 在E1 E2 中,<name>是自由的当且仅当<name>在E1 中约束或<name>在E2 中约束

需要注意的是,一个变量在表达式中可能既自由又约束,考虑下面的这个例子:

(λ x.xy)(λ y.y)

在λ x.xy中,y是自由的,但在λ y.y中y却是约束的。

1.4 替换

在λ 算子中,函数都是没名字的, 这样用起来是很麻烦的事情,每次用函数都需要把它的定义写一遍。 因此,为了简便,我们可以给函数定义起个别名:

f ≡ λ x.x
ff = (λ x.x)(λ z.z)
   = [λ z.z/x]x
   = λ x.x
   = f

当函数中存在自由变量时需要小心变量名的冲突:

(λ x.(λ y.xy))y = λ y.[x/y]y = λ y.yy

注意,在表达式λ y.xy中x为自由变量,这一点很重要。 此时两个y发生了冲突。把函数中的约束变量y改名为t:

(λ x.(λ t.xt))y = λ t.[x/y]t = λ y.yt

最后,总结一下替换规则。 在函数λ x.<exp>应用到表达式E时,把exp中所有的自由的x替换成E。 如果替换会使得自由变量变为约束变量, 那么在替换前应该把冲突的约束变量重命名。

再看一个例子:

(λ x.(λ y.(x (λ x.xy))))y

在(λ y.(x (λ x.xy)))中只有第一个x是自由的,因此只需要替换第一个x即可。 可是替换x为y后x就会变成约束变量,因此需要对约束变量y进行重命名:

[y/x](λ t.(x (λ x.xt))) = (λ t.([y/x]x (λ x.xt))) = (λ t.(y (λ x.xt)))

需要注意,在对λ x.<exp>的替换中,替换的是<exp>中的自由变量x,而不是λ x.<exp>中的变量x, 在λ x.<exp>中不存在自由的x。

另外,在使用函数时,如果给出的参数不足函数定义的参数,那么会产生一个新的函数, 若参数数量足够,则消去对应的λ 部分

example ≡ λ xy.x(y)
example a ≡ (λ xy.x(y))a
          ≡ (lambda y.a(y))
example a b ≡ (λ xy.x(y))a b             ≡ a(b)

2 丘奇数

有了λ 算子,就可以从全新的角度重新定义许多基础的概念了。

通过如下定义可以定义所有的自然数:

0 ≡ λ sz.z
1 ≡ λ sz.s(z)
2 ≡ λ sz.s(s(z))

这样定义的数被称为丘奇数。

有了丘奇数,还可以对其进行运算:

S ≡ λ wyx.y(wyx)
S0 ≡ (λ wyx.y(wyx))(λ sz.z)
   ≡ λ yx.y((λ sz.z)yx)
   ≡ λ yx.y(x)
   ≡ 1
S1 ≡ (λ wyx.y(wyx))(λ sz.s(z))
   ≡ λ yx.y((λ sz.s(z))yx)
   ≡ λ yx.y(y(x))
   ≡ 2

S可以对数进行加一操作。那么如何实现加法呢?

2S1 ≡ (λ sz.s(s(z)))(λ wyx.y(wyx))(λ uv.u(v))
    ≡ λ wyx.y(wyx)(λ wyx.y(wyx)(λ uv.u(v)))
    ≡ SS1
    ≡ λ wyx.y(wyx)(λ yx.y(y(x)))
    ≡ S2
    ≡ 3

在这个例子中,数2把加一操作嵌套了两次,依次作用在1上,得到了3; 同理,当计算m+n是,m会把加一操作嵌套m次并作用在n上,得到的结果就是m+n。

乘法的定义如下:

x ≡ (λ xyz.x(yz))
x 2 2 ≡ (λ xyz.x(yz))2 2
      ≡ λ z.2(2z)
      ≡ λ z.2((λ uv.u(u(v)))z)
      ≡ λ z.2(λ v.z(z(v)))
      ≡ λ z.((λ ab.a(a(b)))(λ v.z(z(v))))
      ≡ λ z.(λ b.(λ v.z(z(v))((λ v.z(z(v)))b)))
      ≡ λ zb.(λ v.z(z(v)) (z(z(b))))
      ≡ λ zb.z(z(z(z(b))))
      ≡ 4

3 逻辑运算

用λ 算子还可以定义逻辑值true和false,并在其上进行操作:

T ≡ λ xy.x
F ≡ λ xy.y

正误的定义很简单,逻辑运算的定义也不复杂:

∧ ≡ λ xy.xyF
∨ ≡ λ xy.xTy
¬ ≡ λ x.xFT

有了逻辑值,就可以定义带条件分支的函数。 现在我们来定义一个函数,使得输入0返回T,输入正数返回F:

Z ≡ λ x.xF¬F

若作用在0上,那么会展开成如下:

Z0 ≡ 0F ¬ F ≡ (λ sz.z)(F ¬)F ≡ ¬ F ≡ T

若作用在正整数N上,则会展开成如下(以2为例子):

Z2 ≡ 2F ¬ F ≡ (λ sz.s(s(z)))(F ¬)F
   ≡ F(F(¬))F ≡ FAF ≡ F
A ≡ F(¬)

如例子所示,无论内部嵌套多少F(F(F(¬))),由于都是FAF的形式,返回结果都是F。

之后,就可以定义比较函数。 在此之前,需要先定义减一函数。

对于任意一个二元组(x, y),可以用如下表达式定义:

λ zxy.xy

z为T时,可以得到该二元组的第一个数,为F时可得第二个数。

现在定义一个函数G,给出二元组(m, n),返回(m+1, m):

G ≡ λ pz.z(S(pT))(pT)

其中p为二元组

那么对数m的减1运算就是对z00二元组进行m次G操作,然后取其第二个元素, 因此减一定义为:

P ≡ λ n.nG(λ p.p00)F

注意,当输入为0时,返回的结果仍为0

此时,x ≥ y可以定义为对y进行x次减一操作得到的结果为0:

GT ≡ λ xy.Z(xPy)

这样,等取操作就是x ≥ y且y ≥ x:

EQ ≡ λ xy.∧ (GT xy)(GT yx)

由大等、等于以及逻辑操作,可以定义小于、小等、不等等一系列比较操作,在此就略过了。

4 递归

在lambda演算中,所有的函数都是匿名的,因此想要实现递归会变的很麻烦。 这里用阶乘函数为例,思考如何解决递归的问题。 先定义一个简单的递归函数:(为了方便,此处使用了if (p) (expt) (expf)表示条件)

factor ≡ λ n.if (EQ n 0) 1 n*(factor n-1)

可是,这个函数是无法使用的,因为factor这个名字在定义中还是无意义的符号。

那么如果把factor函数本身当成一个参数传入不就可以解决问题了吗? 在这种思路下,得到了下列定义:

factor ≡ λ nf.if (Z n 0) 1 n*(f n-1)

在使用时则如下:

factor n factor

可是这样的问题在于,factor有两个参数,可是函数内用的是f n-1,因此无法正常使用。 那么不妨改成这样:

factor ≡ λ nf.if (Z n 0) 1 n*(f n-1 f)

这样就解决了问题,可是这样的写法比较丑陋……

再看一下第一次尝试得出的错误写法,无疑要正常的多。 如果可以使用某种方法让那种写法成立该多好呢?

这时,就需要介绍Y算子了。Y算子定义如下:

Y ≡ λ y.(λ x.y(xx))(λ x.y(xx))

Y算子有一些独特的性质:

YR ≡ (lamdba x.R(xx))(λ x.R(xx))
   ≡ R((λ x.R(xx))(λ x.R(xx)))
   ≡ R(YR)

实际上,YR就是函数R的不动点。有了Y算子以后,我们就可以把上面那种不正确的写法弄正确:

factor ≡ λ nf.if (Z n 0) 1 n*(f n-1)
Y factor 3 ≡ factor (Y factor )3 ≡ 3*(Y factor 2)
    ≡ 3*2*(Y factor 1) ≡ 3*2*1*(Y factor 0) ≡ 3*2*1*1
    ≡ 6

原文地址:https://www.cnblogs.com/w0mTea/p/4244875.html