(在这个帖子的原始版本里,我试图用一个JavaScript工具来生成MathML。但不太顺利:有几个浏览器没法正确的渲染,在RSS feed里也显示的不好。所以我只好从头开始,用简单的文本格式重新写一遍。)

计算机科学,尤其是编程语言,经常倾向于使用一种特定的演算:Lambda演算(Lambda Calculus)。这种演算也广泛地被逻辑学家用于学习计算和离散数学的结构的本质。Lambda演算伟大的的原因有很多,其中包括:

  • 非常简单。
  • 图灵完备。
  • 容易读写。
  • 语义足够强大,可以从它开始做(任意)推理。
  • 它有一个很好的实体模型。
  • 容易创建变种,以便我们探索各种构建计算或语义方式的属性。

Lambda演算易于读写,这一点很重要。它导致人们开发了很多极为优秀的编程语言,他们在不同程度上都基于Lambda演算:LISP,ML和Haskell语言都极度依赖于Lambda演算。

Lambda演算建立在函数的概念的基础上。纯粹的Lambda演算中,一切都是函数,连值的概念都没有。但是,我们可以用函数构建任何我们需要的东西。还记得在这个博客的初期,我谈了一些关于如何建立数学的方法么? 我们可以从无到有地用Lambda演算建立数学的整个结构。

闲话少说,让我们深入的看一看LC(Lambda Calculus)。对于一个演算,需要定义两个东西:语法,它描述了如何在演算中写出合法的表达式;一组规则,让你符号化地操纵表达式。

Lambda演算的语法

Lambda演算只有三类表达式:

  1. 函数定义:Lambda演算中的函数是一个表达式,写成:lambda x . body,表示“一个参数参数为x的函数,它的返回值为body的计算结果。” 这时我们说:Lambda表达式绑定了参数x
  2. 标识符引用(Identifier reference):标识符引用就是一个名字,这个名字用于匹配函数表达式中的某个参数名。
  3. 函数应用(Function application):函数应用写成把函数值放到它的参数前面的形式,如(lambda x . plus x x) y

柯里化

在Lambda演算中有一个技巧:如果你看一下上面的定义,你会发现一个函数(Lambda表达式)只接受一个参数。这似乎是一个很大的局限 —— 你怎么能在只有一个参数的情况下实现加法?

这一点问题都没有,因为函数就是值。你可以写只有一个参数的函数,而这个函数返回一个带一个参数的函数,这样就可以实现写两个参数的函数了——本质上两者是一样的。这就是所谓的柯里化(Currying),以伟大的逻辑学家Haskell Curry命名。

例如我们想写一个函数来实现x + y。我们比较习惯写成类似:lambda x y . plus x y之类的东西。而采用单个参数函数的写法是:我们写一个只有一个参数的函数,让它返回另一个只有一个参数的函数。于是x + y就变成一个单参数x的函数,它返回另一个函数,这个函数将x加到它自己的参数上:

lambda x. ( lambda y. plus x y )

现在我们知道,添加多个参数的函数并没有真正添加任何东西,只不过简化了语法,所以下面继续介绍的时候,我会在方便的时候用到多参数函数。

自由标识符 vs. 绑定标识符

有一个重要的语法问题我还没有提到:闭包(closure)或者叫完全绑定(complete binding)。在对一个Lambda演算表达式进行求值的时候,不能引用任何未绑定的标识符。如果一个标识符是一个闭合Lambda表达式的参数,我们则称这个标识符是(被)绑定的;如果一个标识符在任何封闭上下文中都没有绑定,那么它被称为自由变量。

  • lambda x . plus x y:在这个表达式中,yplus是自由的,因为他们不是任何闭合的Lambda表达式的参数;而x是绑定的,因为它是函数定义的闭合表达式plus x y的参数。
  • lambda x y . y x :在这个表达式中xy都是被绑定的,因为它们都是函数定义中的参数。
  • lambda y . (lambda x . plus x y):在内层演算lambda x . plus x y中,yplus是自由的,x是绑定的。在完整表达中,xy是绑定的:x受内层绑定,而y由剩下的演算绑定。plus仍然是自由的。

我们会经常使用free(x)来表示在表达式x中自由的标识符。

一个Lambda演算表达式只有在其所有变量都是绑定的时候才完全合法。但是,当我们脱开上下文,关注于一个复杂表达式的子表达式时,自由变量是允许存在的——这时候搞清楚子表达式中的哪些变量是自由的就显得非常重要了。

Lambda演算运算法则

Lambda演算只有两条真正的法则:称为Alpha和Beta。Alpha也被称为「转换」,Beta也被称为「规约」。

Alpha转换

Alpha是一个重命名操作; 基本上就是说,变量的名称是不重要的:给定Lambda演算中的任意表达式,我们可以修改函数参数的名称,只要我们同时修改函数体内所有对它的自由引用。

所以 —— 例如,如果有这样一个表达式:

lambda x . if (= x 0) then 1 else x ^ 2

我们可以用Alpha转换,将x变成y(写作alpha[x / y]),于是我们有:

lambda y . if (= y 0) then 1 else y ^ 2

这样丝毫不会改变表达式的含义。但是,正如我们将在后面看到的,这一点很重要,因为它使得我们可以实现比如递归之类的事情。

Beta规约

Beta规约才是精彩的地方:这条规则使得Lambda演算能够执行任何可以由机器来完成的计算。

Beta基本上是说,如果你有一个函数应用,你可以对这个函数体中和对应函数标识符相关的部分做替换,替换方法是把标识符用参数值替换。这听起来很费解,但是它用起来却很容易。

假设我们有一个函数应用表达式:“ (lambda x . x + 1) 3 “。所谓Beta规约就是,我们可以通过替换函数体(即“x + 1”)来实现函数应用,用数值“3”取代引用的参数“x”。于是Beta规约的结果就是“3 + 1”。

一个稍微复杂的例子:(lambda y . (lambda x . x + y)) q。 这是一个挺有意思的表达式,因为应用这个Lambda表达式的结果是另一个Lambda表达式:也就是说,它是一个创建函数的函数。这时候的Beta规约,需要用标识符“q”替换所有的引用参数“y”。所以,其结果是“ lambda x . x + q “。

再给一个让你更不爽的例子:“ (lambda x y. x y) (lambda z . z * z) 3 “。这是一个有两个参数的函数,它(的功能是)把第一个参数应用到第二个参数上。当我们运算时,我们替换第一个函数体中的参数“x”为“lambda z . z * z “;然后我们用“3”替换参数“y”,得到:“ (lambda z . z * z) 3 “。 再执行Beta规约,有“3 * 3”。

Beta规则的形式化写法为:

lambda x . B e = B[x := e] if free(e) subset free(B[x := e])

最后的条件“if free(e) subset free(B[x := e])”说明了为什么我们需要Alpha转换:我们只有在不引起绑定标识符和自由标识符之间的任何冲突的情况下,才可以做Beta规约:如果标识符“z”在“e”中是自由的,那么我们就需要确保,Beta规约不会导致“z”变成绑定的。如果在“B”中绑定的变量和“e”中的自由变量产生命名冲突,我们就需要用Alpha转换来更改标识符名称,使之不同。

例子更能明确这一点:假设我们有一个函数表达式,“ lambda z . (lambda x . x + z) “,现在,假设我们要应用它:

(lambda z . (lambda x . x + z)) (x + 2)

参数“(x + 2)”中,x是自由的。现在,假设我们不遵守规则直接做Beta规约。我们会得到:

lambda x . x + x + 2

原先在“x + 2”中自由的的变量现在被绑定了。再假设我们应用该函数:

(lambda x . x + x + 2) 3

通过Beta规约,我们会得到“3 + 3 + 2”。

如果我们按照应有的方式先采用Alpha转换,又该如何?

  • 由 alpha[x/y] 有: (lambda z . (lambda y . y + z)) (x + 2)
  • 由Beta规约: (lambda y . y + x + 2) 3
  • 再由Beta规约: 3 + x + 2 。

3 + x + 2”和“3 + 3 + 2”是非常不同的结果!

规则差不多就是这些。还有另外一个规则,你可以选择性地加一条被称为Eta-规约的规则,不过我们将跳过它。 我在这里描述了一个图灵完备 —— 完整有效的计算系统。 要让它变得有用,或看它如何用来做些有实际意义的事情,我们还需要定义一堆能让我们做数学计算的基本函数,条件测试,递归等,我将在下一篇文章讨论这些。

我们也还没有定义Lambda-演算的模型呢。(原作者在这里这里讨论了模型的概念。)模型实际上是非常重要的!逻辑学家们在摆弄了LC好几年之后,才为其想出一个完整的模型,这是件非常重要的事情,因为虽然LC看起来是正确的,但在早期为它定义一个模型的尝试,却是失败的。毕竟,请记住,如果没有一个有效的模型,这意味着该系统的结果是毫无意义的!

http://cgnail.github.io/academic/lambda-1/

我的最爱Lambda演算——开篇的更多相关文章

  1. [学习] 从 函数式编程 到 lambda演算 到 函数的本质 到 组合子逻辑

    函数式编程 阮一峰 <函数式编程初探>,阮一峰是<黑客与画家>的译者. wiki <函数编程语言> 一本好书,<计算机程序的构造与解释>有讲到schem ...

  2. 简单易懂的程序语言入门小册子(1):基于文本替换的解释器,lambda演算

    最近比较闲,打算整理一下之前学习的关于程序语言的知识.主要的内容其实就是一边设计程序语言一边写解释器实现它.这些知识基本上来自Programming Languages and Lambda Calc ...

  3. Lambda演算(一)大道至简

    从选择信息专业开始到回炉读书为止,四舍五入码了八年代码.对于计算机科学的认知仅限于: 1)使用不同语言实现特定功能 2)实现不同算法以增进系统性能 3)搭建不同架构进行组织管理   但从未思考一些本质 ...

  4. Lambda演算 - 简述Y组合子的作用

    Y组合子:\f.(\x.f(xx))(\x.f(xx)),接受一个函数,返回一个高阶函数 Y组合子用于生成匿名递归函数. 什么叫匿名递归函数,考虑以下C语言递归函数 int sum(int n) { ...

  5. lambda演算

    先了解下相关的知识点(以下都只用先了解简单的概念,建议wiki): BNF范式,上下文无关文法,函数柯里化. lambda读书笔记演算: http://www.blogjava.net/wxb_nud ...

  6. 简单易懂的程序语言入门小册子(1.5):基于文本替换的解释器,递归定义与lambda演算的一些额外说明

    这一篇接在第一篇lambda演算的后面.讲讲一些数学知识. 经常有些看似很容易理解的东西,一旦要描述得准确无误,就会变得极为麻烦. 软件工程里也有类似情况:20%的代码实现了核心功能,剩下80%的代码 ...

  7. [摘录] 图灵机与lambda演算的关系

    在阅读函数式编程相关资料时,看到如下一段话.感觉说的很好,可以帮助我这种学渣一点点的建立起整个知识体系. 以下片段,摘抄自豆瓣网友 赛义甫 的豆列 “逻辑与计算” 中的一段介绍. 莱布尼兹曾经有两个梦 ...

  8. Haskell语言学习笔记(79)lambda演算

    lambda演算 根据维基百科,lambda演算(英语:lambda calculus,λ-calculus)是一套从数学逻辑中发展,以变量绑定和替换的规则,来研究函数如何抽象化定义.函数如何被应用以 ...

  9. C++ 泛型编程/模板 泛函编程/Lambda/λ演算

    1.泛型编程(C++模板) 其中,Ada, Delpha, Java, C#, Swift 称之为 泛型/generics; ML, Scala和 Haskell 称之为 参数多态/parametri ...

随机推荐

  1. Maven学习总结(6)——Maven与Eclipse整合

    Maven学习总结(六)--Maven与Eclipse整合 一.安装Maven插件 下载下来的maven插件如下图所示:,插件存放的路径是:E:/MavenProject/Maven2EclipseP ...

  2. noip模拟赛 第k大区间

    [问题描述] 定义一个长度为奇数的区间的值为其所包含的的元素的中位数. 现给出n个数,求将所有长度为奇数的区间的值排序后,第K大的值为多少. [输入] 输入文件名为kth.in. 第一行两个数n和k ...

  3. asp.net--webservice--安全验证SoapHeader

    服务端的设置 客户端设置,需要生成一个服务端的代理,调用服务端的方法

  4. [bzoj4530][Bjoi2014]大融合_LCT

    大融合 bzoj-4530 Bjoi-2014 题目大意:n个点,m个操作,支持:两点连边:查询两点负载:负载.边(x,y)的负载就是将(x,y)这条边断掉后能和x联通的点的数量乘以能和y联通的点的数 ...

  5. PHP array_diff_assoc()

    定义和用法 array_diff_assoc() 函数返回两个数组的差集数组.返回的数组的元素都取自被比较的数组(既第一个数组). 和 array_diff() 函数 不同,本函数要求键名和键值都进行 ...

  6. HDU 3666

    此题不难,不等式很空易就列出来了,只是要把它转化成减法形式..卡在这了... 其实取一个log对数就好了...要记住这个技巧.用基于dfs的spfa.. #include<iostream> ...

  7. Hilbert曲线简单介绍及生成算法

    Hilbert曲线 Hilbert曲线是一种填充曲线,相似的填充曲线还包含Z曲线.格雷码等其它方法.Hilbert曲线根据自身空间填充曲线的特性,能够线性地贯穿二维或者更高维度每一个离散单元.而且只穿 ...

  8. USB设备驱动之设备初始化(设备枚举)

    USB设备从接入HUB到正常工作之前.都属于设备枚举阶段.所谓设备枚举.就是让host控制器认识USB设备,并为其准备资源.建立好主机与设备间的数据传递机制. 该阶段的工作,是USB通信协议规定的,所 ...

  9. golang——(strings包)常用字符串操作函数

    (1)func HasPrefix(s, prefix string) bool 判断字符串s是否有前缀字符串prefix: (2)func HasSuffix(s, suffix string) b ...

  10. LocalDateTime相关处理,得到零点以及24点值,最近五分钟点位,与Date互转,时间格式

    最近一直使用LocalDateTime,老是忘记怎么转换,仅此记录一下 import java.time.Instant; import java.time.LocalDateTime; import ...