简单易懂的程序语言入门小册子(3):基于文本替换的解释器,let表达式,布尔类型,if表达式
let表达式
let表达式用来声明一个变量。 比如我们正在写一个模拟掷骰子游戏的程序。 一个骰子有6个面。 所以这个程序多次用到了6这个数字。 有一天,我们忽然改变主意,要玩12个面的骰子。 于是我们不得不仔细查找源代码,把里面的6改成12。 对于一个较大的程序,这是灾难的开始。 有时我们会漏掉几个6,有时我们会把几个指的不是骰子面数的6误改成12。 这种灾难被称作“魔术数字”。 避免魔术数字的方法一般是声明一个变量——比如说变量\(a\)——让这个变量等于6(\(a=6\))。 这个例子的let表达式包含三个元素:变量\(a\),要赋予变量的值6,以及程序主体\(M\)。 我将这条let表达式写成下面的样子: \[ ({let} \; a \; 6 \; M) \] 一般地,定义let表达式为如下形式: \[ ({let} \; X \; N \; M) \] 这是一个单变量的let表达式。
还是掷骰子的例子。 避免魔术数字还有一种方法是定义一个函数,函数的参数是骰子的面数\(a\),函数体是程序主体\(M\): \[ \lambda a.M \] 然后以参数6调用这个函数: \[ (\lambda a.M \; 6) \] 将上面这个表达式与let表达式对比,可以看到let表达式不过是函数调用的语法糖: \[ ({let} \; X \; N \; M) = (\lambda X.M \; N) \] 基于尽量简单的原则,我不打算将let表达式加入到解释器的语法中, 而是让let表达式以宏的形式加入语言。 所以另外写了一个函数translate来展开let表达式。


解释器先调用translate做转换,再调用value-of求值。

布尔类型
加入布尔类型可以像加入整数一样,定义布尔类型为基本类型,然后定义几个和布尔类型相关的表达式。 不过基于“以玩的心态写代码”的原则,我打算折腾一下,用编码的方式引入布尔类型。
可以说,布尔类型唯一的用途就是用于选择(二选一)。可以将真(true)理解为一个“两个中选择第一个”的函数,将假(false)理解为一个“两个中选择第二个”的函数。如下定义布尔类型: \begin{eqnarray*} {true} &=& \lambda x.\lambda y.x \\ {false} &=& \lambda x.\lambda y.y \end{eqnarray*}
为了将整数类型和布尔类型联系起来,需要添加一个判断一个整数是否为零的基本函数iszero。 \begin{eqnarray*} M, N, L &=& ... \\ &|& ({iszero} \; b) \end{eqnarray*} iszero的求值过程: \begin{eqnarray*} eval(({iszero} \; 0)) &=& {true} \\ eval(({iszero} \; b)) &=& {false}, 其中b \neq 0 \end{eqnarray*} 代码:

if表达式
if表达式定义为: \[ ({if} \; L \; M \; N) = ((L \; M) \; N) \]
上面if表达式的定义在call-by-value的调用方式会有问题。 在call-by-value的调用方式下,不论\(L\)的值是真是假,\(M\)和\(N\)都会被求值。 这不仅造成了多余的计算,在一些情况下会很悲剧: 如果\(M\)或者\(N\)有副作用(后面会加入一些有副作用的表达式),很可能会导致结果不正确; 如果这个if表达式在一个递归函数的函数体里,那么调用这个递归函数会无限循环。
为了避免\(M\)和\(N\)被提前求值,这里用一个技巧来延后\(M\)和\(N\)的求值。 将if表达式的定义改为: \begin{eqnarray*} ({if} \; L \; M \; N) &=& (((L \; \lambda X.M) \; \lambda X.N) \; 0) \\ &&其中X \notin FV(M) \cup FV(N) \end{eqnarray*} 将\(M\)和\(N\)封装成\(\lambda X.M\)和\(\lambda X.N\),避免了\(M\)和\(N\)被求值。 等到\(L\)的真假值被求出并选择了\(\lambda X.M\)或\(\lambda X.N\)中的一个后, 将其应用到参数0上(0是随便选的,反正\(X\)这个参数在函数体\(M\)和\(N\)里也用不着)。 这个技巧在很多call-by-value的语言中用来模拟惰性求值。
和let表达式一样,if表达式以宏的形式加入到语言中。 Call-by-name的解释器:

Call-by-value的解释器:

简单易懂的程序语言入门小册子(3):基于文本替换的解释器,let表达式,布尔类型,if表达式的更多相关文章
- 简单易懂的程序语言入门小册子(1):基于文本替换的解释器,lambda演算
最近比较闲,打算整理一下之前学习的关于程序语言的知识.主要的内容其实就是一边设计程序语言一边写解释器实现它.这些知识基本上来自Programming Languages and Lambda Calc ...
- 简单易懂的程序语言入门小册子(1.5):基于文本替换的解释器,递归定义与lambda演算的一些额外说明
这一篇接在第一篇lambda演算的后面.讲讲一些数学知识. 经常有些看似很容易理解的东西,一旦要描述得准确无误,就会变得极为麻烦. 软件工程里也有类似情况:20%的代码实现了核心功能,剩下80%的代码 ...
- 简单易懂的程序语言入门小册子(6):基于文本替换的解释器,引入continuation
当我写到这里的时候,我自己都吃了一惊. 环境.存储这些比较让人耳熟的还没讲到,continuation先出来了. 维基百科里对continuation的翻译是“延续性”. 这翻译看着总有些违和感而且那 ...
- 简单易懂的程序语言入门小册子(5):基于文本替换的解释器,递归,不动点,fix表达式,letrec表达式
这个系列有个显著的特点,那就是标题越来越长.忽然发现今天是读书节,读书节多读书. ==下面是没有意义的一段话============================================== ...
- 简单易懂的程序语言入门小册子(7):基于文本替换的解释器,加入continuation,重构解释器
或许在加入continuation之前要先讲讲费这么大劲做这个有什么意义. 毕竟用不用continuation的计算结果都是一样的. 不过,这是一个兴趣使然的系列,学习这些知识应该完全出于好奇与好玩的 ...
- 简单易懂的程序语言入门小册子(4):基于文本替换的解释器,递归,如何构造递归函数,Y组合子
递归.哦,递归. 递归在计算机科学中的重要性不言而喻. 递归就像女人,即令人烦恼,又无法抛弃. 先上个例子,这个例子里的函数double输入一个非负整数$n$,输出$2n$. \[ {double} ...
- Go语言入门篇-gRPC基于golang & java简单实现
一.什么是RPC 1.简介: RPC:Remote Procedure Call,远程过程调用.简单来说就是两个进程之间的数据交互. 正常服务端的接口服务是提供给用户端(在Web开发中就是浏览器)或者 ...
- C语言入门(2)——安装VS2013开发环境并编写第一个C语言程序
在C语言入门系列中,我们使用Visual studio 2013 Professional作为开发工具.本篇详细介绍如何安装Visualstudio 2013 Professional并写出我们第一个 ...
- 《Java从入门到失业》第一章:计算机基础知识(三):程序语言简介
1.3程序语言简介 我们经常会听到一些名词:低级语言.高级语言.编译型.解释型.面向过程.面向对象等.这些到底是啥意思呢?在正式进入Java世界前,笔者也尝试简单的聊一聊这块东西. 1.3.1低级语言 ...
随机推荐
- Spark之GraphX的Graph_scala学习
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreem ...
- Spring Data Redis示例
说明 关于Redis:一个基于键值对存储的NoSQL内存数据库,可存储复杂的数据结构,如List, Set, Hashes. 关于Spring Data Redis:简称SDR, 能让Spring应用 ...
- MySQL修改表、字段、库的字符集及字符集说明
修改数据库字符集: ALTER DATABASE db_name DEFAULT CHARACTER SET character_name [COLLATE ...]; 把表默认的字符集和所有字符列( ...
- 【精解】EOS TPS 多维实测
本文主要研究EOS的tps表现,会从插件.cleos.EOSBenchTool以及eosjs四种方式进行分析研究. 关键字:eos, tps, cleos, txn_test_gen_plugin, ...
- [HEOI2016] 序列
Description 有n个数,每个数有若干取值,但是只能在原数列的一个位置变换取值,求一个最长上升子序列,满足无论数列如何变化,这都是一个最长上升子序列. Solution 记录 \(l[i],r ...
- [JSOI2010] 连通数
Description Input 输入数据第一行是图顶点的数量,一个正整数N. 接下来N行,每行N个字符.第i行第j列的1表示顶点i到j有边,0则表示无边. Output 输出一行一个整数,表示该图 ...
- openssl rsa/pkey
openssl系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html openssl rsa和openssl pkey分别是RSA密钥的处理工具和 ...
- Linux 强制安装 rpm 包
Linux 正常安装 rpm 的命令是: rpm -ivh xxx.rpm 重复安装时需加属性: 软件包重复安装将会失败,若仍需要安装必须加 --replacepkgs 属性 软件包的某个文件已在安装 ...
- 使用C#编写自己的代码生成器,附代码讲解(一)
使用过代码生成器的开发人员应该知道,通过代码生成器生成项目的代码,可以大大的减少重复编码的时间,提供项目开发的效率,将自己从繁杂重复的代码中解放出来.现在网络上也有很多的开源的代码生成器或者使用比较广 ...
- SQL while循环
ALTER Proc [dbo].[p_GetServerDataCursor] AS BEGIN IF EXISTS ( SELECT * FROM sys.objects WHERE object ...