这一篇接在第一篇lambda演算的后面。讲讲一些数学知识。

经常有些看似很容易理解的东西,一旦要描述得准确无误,就会变得极为麻烦。 软件工程里也有类似情况:20%的代码实现了核心功能,剩下80%的代码处理边界情况。 于是,所谓的准确描述里的大部分文字都在说明边界情况,核心概念只有寥寥几字——好比一件打满补丁的衣服,完全看不出原来的样子。 出现这种现象要么是人类的大脑有缺陷,难以严谨而又准确的理解概念,也就是说人类太笨; 要么就是语言系统有问题,难以简洁地表达概念,而发明不出新的语言系统的人类还是很笨。 无怪乎“人类一思考,上帝就发笑”。 这一节打算尽量严谨地描述$\lambda$演算里的一些符号的含义,希望不会写得太繁琐。

递归定义

实数的链表$List$一般定义为: \begin{equation*}\begin{array}{lcl}   List &=& {nil} \\        &|& \left<R, List\right> \end{array}\end{equation*} 其中,nil是一个空链表,$\left<A, B\right>$表示包含$A$和$B$的二元组,$R$表示实数集合。

这个文法实际上是定义了一个集合$List$,我用集合运算的符号重写$List$的定义: \begin{equation*}\begin{array}{lcl}   List &=& \{{nil}\} \\     &\cup& \{\left<r, l\right> | r \in R, l \in List\} \end{array} \end{equation*} 这是个递归定义。 递归定义对于没接触过的人来说,其实是很难以理解的。 我们现在觉得递归定义很直观,或许只是{\bf习惯了}这种东西而已。 递归定义难以理解的地方在于,我们在描述定义的地方用到了被定义的事物本身。

比如说,我现在想知道$List$是什么。 这时我脑里有$List$这个符号,但是我还不知道$List$代表什么内容。 然后我就来看这个定义,结果看到定义里的第二行用到了$List$,但是我这时候还没不知道什么是$List$呢!

所以,为了具体解释递归定义到底定义了什么,我下面用一种构造性的方法来定义$List$。

首先,基础情况是空链表nil,构造一个只包含nil的集合,记为$List_0$。 \[ List_0 = \{{nil}\} \] 向nil的添加一个元素可以生成只包含一个元素的链表。 所有只包含一个元素的链表的集合再并上$List_0$就是所有元素个数小于等于1的链表的集合,记为$List_1$。 \begin{equation*}\begin{array}{lcl}   List_1 &=& \{{nil}\} \\       &\cup& \{\left<r, l\right> | r \in R, l \in List_0\} \end{array} \end{equation*} 依此类推可以构造包含所有元素个数小于等于2的链表的集合$List_2$,所有元素个数小于等于3的链表的集合$List_3$…… 一般地,可以构造包含所有元素个数小于等于$i$($i>0$)的链表的集合$List_i$: \begin{equation*}\begin{array}{lcl}   List_i &=& \{{nil}\} \\       &\cup& \{\left<r, l\right> | r \in R, l \in List_{i-1}\} \end{array} \end{equation*} 当$i$趋于无穷时,我们就得到了链表的集合$List$: \[ List = \lim_{i \rightarrow \infty}{List_i} \] 顺便一提,这里用到了极限,或许还得解释解释集合的极限是什么意思,这个极限收不收敛等问题。 不过,管他的,理解是什么意思就行。 可以看到,递归定义是一个涉及到“无穷”的定义。 很多东西一旦涉及到无穷就很麻烦。

在$List_i$的定义中让$i$趋向无穷看看会怎样? 由于当$i$趋向无穷时有$List_i = List_{i-1} = List$,所以我们得到了$List$的递归定义。

下面用同样的思路描述了$\lambda$演算的语法: \begin{equation*}\begin{array}{lcl}   M_0 &=& X \\   M_i &=& X \\    &\cup& \{\lambda x.m | x \in X, m \in M_{i-1}\} \\    &\cup& \{(m \; n) | m \in M_{i-1}, n \in M_{i-1}\} \\   M &=&\displaystyle \lim_{i \rightarrow \infty}{M_i} \end{array} \end{equation*}

归约派生的等价关系

数学让人如此讨厌大概是因为它严谨得像处女座一样。 在我们计算$\lambda$演算的表达式时,我们这样写: \begin{equation*}\begin{array}{lcl}   ((\lambda y.\lambda x.(x \; y) \; a) \; \lambda z.z) &=& (\lambda x.(x \; a) \; \lambda z.z) \\   &=& (\lambda z.z \; a) \\   &=& a \end{array} \end{equation*} 嗯?等号$=$具体是什么意思呢?它是$\alpha$归约、$\beta$归约和$\eta$归约的某种混合。 下面解释“某种混合”具体是什么意思。

归约($\alpha$归约、$\beta$归约和$\eta$归约)实质上是一个集合,这个集合的元素是二元组$\left<m, n\right>$。 这个二元组的意思是$m$可以归约到$n$。 这里混合这三种归约的关系记为符号$\rightarrow$。 “混合”是并集的意思。 $\rightarrow$定义为: \begin{equation*}\begin{array}{lcl} \rightarrow &=& \rightarrow_\alpha \cup \rightarrow_\beta \cup \rightarrow_\eta \end{array} \end{equation*} 简单来说,就是$m \rightarrow n$当且仅当$m \rightarrow_\alpha n$或$m \rightarrow_\beta n$或$m \rightarrow_\eta n$。

$\rightarrow$是不是$=$呢? 如果是的话我就不会特地选用其他符号了。 我们知道,一个等价关系$=$是一个满足以下三种特性的关系:

  1. 自反性:$m = m$,也就是自己等于自己;
  2. 对称性:如果$m = n$,那么$n = m$;
  3. 传递性:如果$m = n$并且$n = l$,那么$m = l$。

用这三个特性扩展$\rightarrow$。扩展后的关系记为$\approx$: \begin{equation*}\begin{array}{rcl}   m &\approx& m \\   m \rightarrow n &\Rightarrow& m \approx n \\   m \approx n &\Rightarrow& n \approx m \\   m \approx n, n \approx l &\Rightarrow& m \approx l \end{array} \end{equation*}

$\approx$还不是我们需要的等号$=$! 看下面例子: \begin{equation*}\begin{array}{rclr}   (\underline{(\lambda y.\lambda x.(x \; y) \; a)} \; \lambda z.z)     &\approx& (\underline{\lambda x.(x \; a)} \; \lambda z.z) & \text{错!}\\   (\lambda y.\lambda x.(x \; y) \; a) &\approx& \lambda x.(x \; a) & \text{对!} \end{array} \end{equation*} 看到这两个表达式的区别吗? $\approx$只能在“最外层”归约!它不能归约子表达式。

所以,还要在$\approx$的基础上添加最后一个特性,添加后就是$=$了: \begin{equation*}\begin{array}{rcl}   m \approx m' &\Rightarrow& m = m' \\   m = m' &\Rightarrow& \lambda x.m = \lambda x.m' \\   m = m' &\Rightarrow& (m \; n) = (m' \; n) \\   n = n' &\Rightarrow& (m \; n) = (m \; n') \end{array} \end{equation*} 在一个大的表达式中,除去一个子表达式以外的部分叫做这个子表达式的上下文。 上面这个特性的意思就是,一个子表达式的归约和它的上下文无关。

终于讲完$=$是什么意思了。 但是还有一个“小问题”。 $=$的计算过程并非唯一的。 比如下面这个例子: \begin{equation*}\begin{array}{lcl}   (\lambda x.x \; \underline{(\lambda y.y \; a)}) &=& \underline{(\lambda x.x \; a)} = a \\   \underline{(\lambda x.x \; (\lambda y.y \; a))} &=& \underline{(\lambda y.y \; a)} = a \end{array} \end{equation*} 一个先计算参数$(\lambda y.y \; a)$,一个则先在最外层做归约。 计算过程不同产生一个问题: 不同计算过程计算出的答案是否是相同(允许$\alpha$归约意义下的相同,如$\lambda x.x$和$\lambda y.y$算一样)的? 关注程序语言这一领域的人或许有听过Church-Rosser定理。 这个定理的推论就是这个问题的肯定回答。

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

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

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

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

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

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

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

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

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

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

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

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

    递归.哦,递归. 递归在计算机科学中的重要性不言而喻. 递归就像女人,即令人烦恼,又无法抛弃. 先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$. \[ {double} ...

  7. 【C语言入门教程】7.3 结构体指针的定义和引用

    C 语言中指针的操作非常灵活,它也能指向结构体变量对结构体变量进行操作.在学习结构指针之前,需要再次加深对指针的认识.声明指针变量时所使用的数据类型修饰符实际上的作用是定义指针访问内存的范围,如果指针 ...

  8. 【C语言入门教程】7.1 结构体类型变量的定义和引用

    前面学习了变量和数组这些简单的数据结构,它们的特点是必须使用规定的数据类型.例如数组被定义为整型后,它的所有存储单元都是由整型构成.现实生活中某一类事物的共同属性可能是由不同的数据类型组成的集合,或者 ...

  9. 【C语言入门教程】7.2 结构体数组的定义和引用

    7.2 结构体数组的定义和引用 当需要使用大量的结构体变量时,可使用结构体定义数组,该数组包含与结构体相同的数据结构所组成的连续存储空间.如下例所示: struct student stu_a[50] ...

随机推荐

  1. 浅谈JavaScript之事件(上)

    一  简述JavaScript及其在浏览器中的地位 (一)  浏览器主要构成 虽然不同浏览器之间存在差异(如Google Chrome,Firefox,Safari和IE等),但单从浏览器构成来说,大 ...

  2. Python和Java编程题(六)

    1.题目:猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不瘾,又多吃了一个 第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下的一半零一个.到第10天早上想再吃时, ...

  3. Go Web:数据存储(3)——gob对象序列化

    序列化持久存储gob 1.内存存储 2.CSV文件存储 3.gob序列化存储 本篇文章仍然接前面的文章:内存存储,主要介绍将博客文章数据序列化持久到文件中. encoding/gob包用于编码器和解码 ...

  4. vue_drf之实现短信验证码

    一.需求 1,需求 我们在做网站开发时,登录页面很多情况下是可以用手机号接收短信验证码,然后实现登录的,那我们今天就来做一做这一功能. 伪代码: 进入登录页面,点击短信登录 输入手机号码,点击获取验证 ...

  5. Git+Gitlab+Ansible剧本实现一键部署动态网站(二)--技术流ken

    项目前言 之前已经写了一篇关于git和ansible的博客<Git+Gitlab+Ansible剧本实现一键部署Nginx--技术流ken>.关于git,gitliab,ansible在我 ...

  6. 在C#中使用ZBar识别条形码

    目录: 一.识别库 二.从一张图片中提取多个条形码 三.注意事项 从博客园学了很多,本着分享的目的,希望后来者遇到类似问题时,不必重复造轮子,早点下班回家^-^. 一.识别库 目前主流的识别库主要有Z ...

  7. “笨方法”学习Python笔记(1)-Windows下的准备

    Python入门书籍 来自于开源中国微信公众号推荐的一篇文章 全民Python时代,豆瓣高级工程师告诉你 Python 怎么学 问:请问你目前最好的入门书是那本?有没有和PHP或者其他语言对比讲Pyt ...

  8. [转]Ubuntu18.04下使用Docker Registry快速搭建私有镜像仓库

    本文转自:https://blog.csdn.net/BigData_Mining/article/details/88233015 1.背景 在 Docker 中,当我们执行 docker pull ...

  9. [转]Rancher 1.6 Docs

    本文转自:https://rancher.com/docs/rancher/v1.6/zh/quick-start-guide/ 快速安装指南 在本节中,我们将进行简单快速的Rancher安装,即在一 ...

  10. Wpf学习20180605

    Windows Presentation Foundation 窗口展示框架 WPF. 与winform界面程序比较,我认为最大的区别是‘与分辨率无关’这个特性. 传统winform程序在低分辨率的电 ...