• 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

  • 相关阅读:
    Delphi 调用DLL TStream作为参数
    Delphi 退出时注销子窗口应注意的问题
    Excel文件转换成用友实施工具Excel格式
    Delphi Function 返回值忘记默认赋值的一些问题
    vscode 安装golang插件方法
    git merge
    vant ui 双向输入框禁止手机键盘弹出
    回调地狱
    vue 数组对象循环添加一个属性 在页面上动态渲染时更改属性值
    Windows下安装Redis,并设置开机自动启动
  • 原文地址:https://www.cnblogs.com/w0mTea/p/4244875.html
Copyright © 2020-2023  润新知