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

先上个例子,这个例子里的函数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. java ReentrantLock

    介绍 ReentrantLock称为重入锁,比内部锁synchonized拥有更强大的功能,它可中断.可定时.设置公平锁 [注]使用ReentrantLock时,一定要释放锁,一般释放放到finnal ...

  2. spring-session用redis实现session共享实践

    什么是spring session? Spring Session provides an API and implementations for managing a user’s session ...

  3. .Net Core 中间件之静态文件(StaticFiles)源码解析

    一.介绍 在介绍静态文件中间件之前,先介绍 ContentRoot和WebRoot概念. ContentRoot:指web的项目的文件夹,包括bin和webroot文件夹. WebRoot:一般指Co ...

  4. 机器学习排序算法:RankNet to LambdaRank to LambdaMART

    使用机器学习排序算法LambdaMART有一段时间了,但一直没有真正弄清楚算法中的所有细节. 学习过程中细读了两篇不错的博文,推荐给大家: 梯度提升树(GBDT)原理小结 徐博From RankNet ...

  5. 第2章 细说Linux系统用户/组管理(1)

    2.1 用户和组的基本概念 用户和组是操作系统中一种身份认证资源. 每个用户都有用户名.用户的唯一编号uid(user id).所属组及其默认的shell,可能还有密码.家目录.附属组.注释信息等. ...

  6. 翻译:赋值操作符(:=)(已提交到MariaDB官方手册)

    本文为mariadb官方手册:赋值操作符(:=)的译文. 原文:https://mariadb.com/kb/en/assignment-operator/ 我提交到MariaDB官方手册的译文:ht ...

  7. [转]Angular4首页加载慢优化之路

    本文转自:https://blog.csdn.net/itest_2016/article/details/80048398 Angular是一个比较完善的前端MVC框架,包含了模板,数据双向绑定,路 ...

  8. FormCollection collection 使用

    参考文档:https://www.cnblogs.com/dare/p/9173479.html

  9. [android] activity横竖屏切换的生命周期

    模拟器横竖屏切换,ctrl+f11 界面activity会销毁,重新打开创建 第一种做法: 定死就是横屏 在清单文件,<activity/>节点部分,添加属性,设置屏幕朝向 android ...

  10. Hibernate-在Eclipse(Oxygen)中安装Hibernatetools插件

    Eclipse(Luna)中是没有Hibernate插件的,该插件是需要自己进行添加. 在网上找了一下关于如何在Eclipse中安装Hibernatetools插件的方法,很多都是先找到Hiberna ...