简单易懂的程序语言入门小册子(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 SQL 性能优化再进一步:CBO 基于代价的优化
摘要: 本文将介绍 CBO,它充分考虑了数据本身的特点(如大小.分布)以及操作算子的特点(中间结果集的分布及大小)及代价,从而更好的选择执行代价最小的物理执行计划,即 SparkPlan. Spark ...
- redis cluster集群web管理工具 relumin
redis cluster集群web管理工具 relumin 下载地址 https://github.com/be-hase/relumin 只支持redis cluster模式 java环境 tar ...
- EL表达式报错: According to TLD or attribute directive in tag file, attribute value does not accept any expressions
EL表达式报错: According to TLD or attribute directive in tag file, attribute value does not accept any ex ...
- Linux命令yum和rpm
yum命令使用 可以简化软件安装命令 yum可以做软件的 1自动安装,安装软件的时候会自动安装需要的依赖 yum install 软件名如安装epel源yum install epel-release ...
- leetcode — remove-nth-node-from-end-of-list
/** * Source : https://oj.leetcode.com/problems/remove-nth-node-from-end-of-list/ * * Created by lve ...
- elk + filebeat,6.3.2版本简单搭建,实现我们自己的集中式日志系统
前言 刚从事开发那段时间不习惯输出日志,认为那是无用功,徒增代码量,总认为自己的代码无懈可击:老大的叮嘱.强调也都视为耳旁风,最终导致的结果是我加班排查问题,花的时间还挺长的,要复现问题.排查问题等, ...
- MyBatis从入门到放弃二:传参
前言 我们在mapper.xml写sql,如果都是一个参数,则直接配置parameterType,那实际业务开发过程中多个参数如何处理呢? 从MyBatis API中发现selectOne和selec ...
- Task.Run Vs Task.Factory.StartNew 【收藏】
在.Net 4中,Task.Factory.StartNew是启动一个新Task的首选方法.它有很多重载方法,使它在具体使用当中可以非常灵活,通过设置可选参数,可以传递任意状态,取消任务继续执行,甚至 ...
- C# 字符串大写转小写,小写转大写,数字保留,其他除外
又是一道面试题,我只想到两种方式: 第一种:循环字符串,判断每个字符串的类型,再根据类型对该字符进行操作(转大写.转小写.不变或舍弃) static void Main(string[] args) ...
- slf4j日志的使用
slf4j(simple logging facade for Java)是Java的简单的日志门面,它不是具体的日志解决方案,它只服务于各种各样slf4j-logo的日志系统.这里的slf4j-lo ...