递归。哦,递归。 递归在计算机科学中的重要性不言而喻。 递归就像女人,即令人烦恼,又无法抛弃。

先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$。 \[ {double} = \lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ({double} \; (- \; n \; 1)))) \]

现在的问题是,这个递归函数在我们的语言里没法直接定义。 我说的直接定义是指像这个用let表达式: \[ ({let} \; {double} \; \lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ({double} \; (- \; n \; 1)))) \; M) \] 把这个let表达式宏展开会看得更清楚些: \[ (\lambda {double}.M \; \lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ({double} \; (- \; n \; 1))))) \] $\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ({double} \; (- \; n \; 1))))$ 里的double是个自由变量。 解释器求值到这里的时候,根本不知道double指的是什么函数。

如何构造递归函数

获得递归的一个关键是如何在函数体中找到自己(结合一开始的比喻,这句话好像蕴含了其他意义深远的意思)。 一个简单的方法是在double上增加一个参数(一般就是第一个参数),把自己传入参数。 把这个修改后的函数叫做mkdouble1吧。 先不考虑mkdouble1的定义,先观察mkdouble1的行为。 因为调用mkdouble1要把自己作为第一个参数传入,所以调用递归函数应该这样写: \[ (({mkdouble1} \; {mkdouble1}) \; n) \] 也就是说,double就是$({mkdouble1} \; {mkdouble1})$。 \begin{eqnarray*}   {double} &=& ({mkdouble1} \; {mkdouble1}) \\               &=& (\lambda v.(v \; v) \; {mkdouble1}) \end{eqnarray*} 最后一步变换是为了让mkdouble1只出现一次。

现在来考虑mkdouble1的定义。 在double上增加一个参数$f$: \[ {mkdouble1} = \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ({double} \; (- \; n \; 1)))) \] 函数调用的时候传入参数$f$的是mkdouble1。 也就是说$f$代表的是mkdouble1。 因此,函数体里递归调用的double用$(f \; f)$替换: \[ {mkdouble1} = \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ((f \; f) \; (- \; n \; 1)))) \] 所以double的定义是: \[ {double} = (\lambda v.(v \; v) \; \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ((f \; f) \; (- \; n \; 1))))) \] 这个定义可以用之前实现的解释器运行。 测试一下:

'(let double ((lambda v (v v))
(lambda f
(lambda n
(if (iszero n) 0 (+ 2 ((f f) (- n 1)))))))
(double 4)) >> 8

Y组合子

这一小节比较理论,知道个思路就行了。所以我就随便写写。 好学的人可以自己查资料(Programming Languages and Lambda Calculi, The Little Schemer)。

mkdouble1并不能让人很满意,因为它不优雅(都是时臣的错)。 mkdouble1递归调用的地方用的是$(f \; f)$,而比较好看比较符合直觉的应该只有一个$f$。 定义这个所谓的比较好看的函数mkdouble如下: \[ {mkdouble} = \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; (f \; (- \; n \; 1)))) \] 我们希望能从mkdouble得到递归函数double。 这是能做到的。只要在利用mkdouble1的double定义上做几个简单的推导就行了: \begin{eqnarray*} {double} &=& (\lambda v.(v \; v) \; \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; ((f \; f) \; (- \; n \; 1))))) \\ &=& (\lambda v.(v \; v) \; \lambda f.(\lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; (f \; (- \; n \; 1)))) \; (f \; f))) \\ &=& (\lambda x.(\lambda v.(v \; v) \; \lambda f.(x \; (f \; f))) \; \lambda f.\lambda n.({if} \; ({iszero} \; n) \; 0 \; (+ \; 2 \; (f \; (- \; n \; 1))))) \\ &=& (\lambda x.(\lambda v.(v \; v) \; \lambda f.(x \; (f \; f))) \; {mkdouble}) \end{eqnarray*}

$\lambda x.(\lambda v.(v \; v) \; \lambda f.(x \; (f \; f)))$被称作Y组合子,记为Y。 然后有: \[ {double} = ({Y} \; {mkdouble}) \]

Y组合子可以用来构造递归函数。 不过上面的定义在call-by-value的调用方式下会进入无限循环。 具体原因就不讲了,只讲结论:问题出在$(f \; f)$这里,对$(f \; f)$做一个$\eta$逆归约就行了。 修改后的Y组合子记为${Y}_{v}$: \[ {Y}_{v} = \lambda x.(\lambda v.(v \; v) \; \lambda f.(x \; (\lambda u.((f \; f) \; u)) \]

测试一下。 Call-by-value的测试:

'(let Y (lambda x
((lambda v (v v))
(lambda f (x (lambda u ((f f) u))))))
(let mkdouble (lambda f
(lambda n
(if (iszero n)
0
(+ 2 (f (- n 1))))))
((Y mkdouble) 4))) >> 8

Call-by-name的测试:

'(let Y (lambda x
((lambda v (v v))
(lambda f (x (f f)))))
(let mkdouble (lambda f
(lambda n
(if (iszero n)
0
(+ 2 (f (- n 1))))))
((Y mkdouble) 4))) >> 8

简单易懂的程序语言入门小册子(4):基于文本替换的解释器,递归,如何构造递归函数,Y组合子的更多相关文章

  1. 简单易懂的程序语言入门小册子(5):基于文本替换的解释器,递归,不动点,fix表达式,letrec表达式

    这个系列有个显著的特点,那就是标题越来越长.忽然发现今天是读书节,读书节多读书. ==下面是没有意义的一段话============================================== ...

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

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

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

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

  4. 简单易懂的程序语言入门小册子(3):基于文本替换的解释器,let表达式,布尔类型,if表达式

    let表达式 let表达式用来声明一个变量. 比如我们正在写一个模拟掷骰子游戏的程序. 一个骰子有6个面. 所以这个程序多次用到了6这个数字. 有一天,我们忽然改变主意,要玩12个面的骰子. 于是我们 ...

  5. 简单易懂的程序语言入门小册子(7):基于文本替换的解释器,加入continuation,重构解释器

    或许在加入continuation之前要先讲讲费这么大劲做这个有什么意义. 毕竟用不用continuation的计算结果都是一样的. 不过,这是一个兴趣使然的系列,学习这些知识应该完全出于好奇与好玩的 ...

  6. 简单易懂的程序语言入门小册子(6):基于文本替换的解释器,引入continuation

    当我写到这里的时候,我自己都吃了一惊. 环境.存储这些比较让人耳熟的还没讲到,continuation先出来了. 维基百科里对continuation的翻译是“延续性”. 这翻译看着总有些违和感而且那 ...

  7. Go语言入门篇-gRPC基于golang & java简单实现

    一.什么是RPC 1.简介: RPC:Remote Procedure Call,远程过程调用.简单来说就是两个进程之间的数据交互. 正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者 ...

  8. C语言入门(2)——安装VS2013开发环境并编写第一个C语言程序

    在C语言入门系列中,我们使用Visual studio 2013 Professional作为开发工具.本篇详细介绍如何安装Visualstudio 2013 Professional并写出我们第一个 ...

  9. 《Java从入门到失业》第一章:计算机基础知识(三):程序语言简介

    1.3程序语言简介 我们经常会听到一些名词:低级语言.高级语言.编译型.解释型.面向过程.面向对象等.这些到底是啥意思呢?在正式进入Java世界前,笔者也尝试简单的聊一聊这块东西. 1.3.1低级语言 ...

随机推荐

  1. 一个案例彻底弄懂如何正确使用 mysql inndb 联合索引

    有一个业务是查询最新审核的5条数据 SELECT `id`, `title` FROM `th_content` WHERE `audit_time` < 1541984478 AND `sta ...

  2. java中Char到底是什么格式的编码

    文本处理中经常有这样的逻辑: String s = new String(bts, "UTF-8"); 看String源代码,里面是一个char[],将bts按照某种编码方式,变成 ...

  3. XML概念定义以及如何定义xml文件编写约束条件java解析xml DTD XML Schema JAXP java xml解析 dom4j 解析 xpath dom sax

    本文主要涉及:xml概念描述,xml的约束文件,dtd,xsd文件的定义使用,如何在xml中引用xsd文件,如何使用java解析xml,解析xml方式dom sax,dom4j解析xml文件 XML来 ...

  4. webservice的两种调用方式

    如下 using ConsoleApplication1.TestWebService; using System; using System.Collections; using System.Co ...

  5. 监控 | open-falcon | 安装

    监控 | open-falcon | 安装 1. 简介 主要看中了它的水平扩展,画图比zabbix要友好,告警支持简单压缩. 绿色:基础组件: 蓝色:作图链路 红色:报警链路 橙色:域名 架构 1.1 ...

  6. Java 8 ThreadLocal 源码解析

    Java 中的 ThreadLocal是线程内的局部变量, 它为每个线程保存变量的一个副本.ThreadLocal 对象可以在多个线程中共享, 但每个线程只能读写其中自己的副本. 目录: 代码示例 源 ...

  7. python列表类型

    列表类型简介 列表类型是一个容器,它里面可以存放任意数量.任意类型的数据. 例如下面的几个列表中,有存储数值的.字符串的.内嵌列表的.不仅如此,还可以存储其他任意类型. >>> L ...

  8. python可变对象和不可变对象的解释

    数据类型分为可变.不可变.可变对象表示可以原处修改该数据对象,不可变对象表示必须创建新对象来保存修改后的数据. 在基础数据类型中: 数值.字符串.元组.frozenset是不可变对象 列表.set.d ...

  9. mvc 中合并两个list集合

    有时候,在进行查询操作的时候需要将从数据库中查询的两张表进行合并,成为一张表然后返回给前端.或者在原有的一张表基础上面加几个新的字段. 这个时候可以新建一个.class[model类],在这个新建的m ...

  10. T-SQL :SQL Server 定义数据完整性 6大约束(三)

    1.创建一客户张表 IF OBJECT_ID('dbo.Employees', 'U') IS NOT NULL DROP TABLE dbo.Employees; CREATE TABLE dbo. ...