Datatype-programming in Racket without structs

在 ML 语言中,我们使用 datatype binding 来实现对 标签联合类型的构建传送门

这是因为 ML 语言中的 list 中的元素类型是统一的

这里是一份经典的 ML 实现标签联合类型 exp 以及提取 exp 类型实际值的函数 eval 的代码

datatype exp = Constant of int
| Negate of exp
| Add of exp*exp
| Multiply of exp*exp
fun eval x =
case x of
Constant e => e
| Negate e => ~ (eval e)
| Add (e1, e2) => eval e1 + eval e2
| Multiply (e1, e2) => (eval e1) * (eval e2)

而得益于 Racket 的 dynamic typing 特性,我们可以直接通过 list 实现标签联合类型的构建

先介绍 Racket 中的一个 feature:Symbols

创建一个 symbol 的方法是 '+symbol名,用 eq? 来比较两个 symbol 是否相同

定义 constructor:

(define (Const i) (list 'Const i))
(define (Negate e) (list 'Negate e))
(define (Add e1 e2) (list 'Add e1 e2))
(define (Multiply e1 e2) (list 'Multiply e1 e2))

定义类型分析函数:

(define (Const? x) (eq? (car x) 'Const))
(define (Negate? x) (eq? (car x) 'Negate))
(define (Add? x) (eq? (car x) 'Add))
(define (Multiply? x) (eq? (car x) 'Multiply))

定义提取值函数:

(define (Const-int e) (car (cdr e)))
... ; 剩余的相似

定义 eval 函数:注意,这里的 eval 函数返回的是一个 Const

(define (eval-exp e)
(cond [(Const? e) e]
[(Negate? e) (Const (- Const-int (eval-exp (Negate-e e)))))]
[(Add? e) (let ([v1 (Const-int (eval-exp (Add-e1 e)))]
[v2 (Const-int (eval-exp (Add-e2 e)))])
(Const (+ v1 v2)))]
[(Multiply? e) (let ([v1 (Const-int (eval-exp (Multiply-e1 e)))]
[v2 (Const-int (eval-exp (Multiply-e2 e)))])
(Const (* v1 v2)))]
[#t (error "eval-exp expect an exp")]))

Datatype-programming in Racket with structs

在 racket 中,除了利用 list 来实现 each-of type ,还有一个 feature 可供选用:那就是 struct

我们这样定义一个 struct : (struct foo (bar baz quuz) #:transparent)

其中 foo 是这个 struct 的名称,bar,baz,quuz分别是各个 field 的名称

这里就同 ML 中的 record 很相似了,不同的是 record 中的每一个 field 需要声明类型,而 racket 完全不需要,可以容纳任意类型的数据

定义了一个 struct,便会生成对应的三个函数:

struct "constructor": 返回一个 foo,其 bar,baz,quuz 领域的值分别是 e1,e2,e3

(foo e1 e2 e3)

类型判断函数:如果 e 是一个 foo,返回 true

(foo? e)

提取值函数:structName-fieldName 返回该 struct 中对应 field 中存储的值

(foo-bar e)
(foo-baz e)
(foo-quuz e)

这三类函数即是我们在用 list 实现时定义的三类函数

那么接下来我们用 struct 实现自己的 eval-exp 函数

(struct const (int) #:transparent)
(struct negate (e) #:transparent)
(struct add (e1 e2) #:transparent)
(struct multiply (e1 e2) #:transparent) (define (eval-exp e)
(cond [(const? e) e]
[(negate? e) (const (- const-int (eval-exp (negate-e e)))))]
[(add? e) (let ([v1 (const-int (eval-exp (add-e1 e)))]
[v2 (const-int (eval-exp (add-e2 e)))])
(const (+ v1 v2)))]
[(multiply? e) (let ([v1 (const-int (eval-exp (multiply-e1 e)))]
[v2 (const-int (eval-exp (multiply-e2 e)))])
(const (* v1 v2)))]
[#t (error "eval-exp expect an exp")]))

其实是和上面用 list 实现的代码一致的,只不过这些函数无需自己定义,直接被 struct 生成了

另外,标识符 #:transparent 代表在 REPL 中打印出完整的 struct

#:mutable 代表声明的 struct 可以被函数 set-structName-fieldName! 进行修改

Why the struct approach is BETTER

首先需要强调的是,struct 实现 不是 list 实现的语法糖。与 list 不同,struct 实际上创造了 new type of value

对于该 struct : (struct add (e1 e2) #:transparent)

除了 add? 之外的类型判定函数 (如 pair?, number? 等等) 都不会返回 true

而 list 实现由于本质上是个 list,用 pair? 进行判定仍会返回 true

另外,struct 实现的抽象性也明显更优

在 struct 实现中,访问 field 中的值只能够通过自带的 accessor

而 list 实现中则可以使用 cdr,car 等等 list 函数来访问,这大大降低了抽象性

Implementing a programming language in general

介绍一下一个语言的一般实现过程 (Language implementation),我们称这个语言为 \(B\) 语言

首先,代码文本经过 parser (解析器,语法分析器) 的检查,排除有语法错误 (括号不匹配,关键字错误) 的代码

生成 Abstract Syntax Tree (抽象语法树)。这是源代码语言结构的一种树形抽象表示,其中树上的每个结点代表源代码中的一种结构

接下来,如果这种语言有类型检查机制的话,type-checker 会对这颗 AST 进行检查

剩下的语言实现 (the rest of the implementation) 一般采取两种方式,有时也会采取这两种方式的混合:

  • interpreter (解释器):我们用 \(A\) 语言编写一个称为解释器的程序,它读入 \(B\) 语言程序的 AST 并生成相应的结果

    在这个意义上,将这个程序被称为 \(evaluator\) 或者 \(executor\) 可能更为合适。但 \(interpreter\) 这个称呼已经成为了学术共识。
  • compiler (编译器):我们用 \(A\) 语言编写一个称为编译器的程序,它读入 \(B\) 语言程序的 AST 并生成一段与其完全等价 (equivalent) 的 \(C\) 语言程序。再利用某些已有的 \(C\) 语言实现 (pre-existing implementation for C) 生成相应结果。在这里,我们称 \(B\) 为 source language (源语言),\(C\) 为 target language (目标语言)

在这两种实现里,用来编写解释器与编译器来实现 \(B\) 语言实现的 \(A\) 语言被称为 metalanguage (元语言)

A one-sentence sermon: Interpreter versus compiler is a feature of a particular programming-language implementation, not a feature of the programming language.

也就是说,编译器或者解释器是语言实现的两种方式,与语言本身无关

我们称 \(C/C++\) 为解释型语言,函数式语言 (functional language) 为编译型语言只是这些语言大多数情况下的实现,但其实为 \(C/C++\) 语言编写一个解释器也是毫无问题的

Implementing a programming language using Rackets

上面的例子就是一个很简单的 interpreter 模型

需要被解释的语言是由 const, negate, add, mutiply 四个关键字组成的算术表达式 (arithmetic expression)

eval-exp 函数则是 interpreter

而编写该 interpreter 的元语言 (metalanguage) 则是 racket

下面我们将利用 Racket 为一个虚拟的编程语言编写 interpreter

假定给定的 AST 是合法的 (这意味着忽略 parser),且不考虑类型错误 (不编写 type-checker,即使出现了实现语言报错 (implementation-depentent error) 的 crash 都没问题)

Interpreter for language with variables need environments

在上面的例子中,我们的算术表达式语言一个最大的缺陷就是没有变量 (variable)

由于没有变量的存在,我们的 interpreter 也十分简单,一个简单的函数就可以搞定了

那么对于含有变量的语言,我们的 interpreter 应该如何编写?

在 week 1 中我们学过,对于某个含有变量的表达式 (expression with variables),我们需要在对应的 环境 (environment that maps variables to values) 中进行 evaluate

所以我们的 interpreter 中必须定义一个辅助函数,读入某个 expression 与某个 environment 并输出对应的结果

在我们自己的 interpreter 中,我们选择牺牲效率,以最形象的方式来表达一个 environment:由若干个 string (变量名) 与 value 对组成的 association list

对于某个给定的环境,interpreter 在以下三种情况处理方式不同:

  • To evaluate a variable expression, it looks up the variable’s name (i.e., the string) in the environment.
  • To evaluate most subexpressions, such as the subexpressions of an addition operation, the interpreter

    passes to the recursive calls the same environment that was passed for evaluating the outer expression.
  • To evaluate things like the body of a let-expression, the interpreter passes to the recursive call a slightly

    different environment, such as the environment it was passed with one more binding (i.e., pair of string

    and value) in it.

简单概括,就是 含 variable 的 expressionsubexpression 以及 let-expression 的 body 部分三种情况

Implementing closures

除了变量之外,一个编程语言中必不可少的部分还有函数

回忆之前关于 function closurelexical scope 的知识:调用 (call) 函数时函数体的环境并不是调用时的环境 (current environment),而是函数被定义时的环境 (the environment when function is difined)。因此,我们的 interpreter 需要 “记忆” 某个函数被定义时所处的环境。

因此我们可以我们创造一个新的数据结构 closure 来储存 interpret 某个函数后的结果,它包含函数本身和定义时所处的环境。

(struct closure (env fun) #:transparent)

接下来我们处理调用函数的情况。对于一个函数调用 (call e1 e2)

  • 在当前环境 (current environment) 下 evaluate e1,结果应为一个 closure (否则出现 run-time error)
  • 在当前环境 (current environment) 下 evaluate e2,结果将作为上述 closure 的参数 (argument)
  • closure 包含两个部分:代码部分与环境部分,我们用环境部分加上调用方传入的参数形成的环境 evaluate 代码的主体部分。

    (原文:We evaluate the body of the code part of the closure using the environment part of the closure extended with the argument of the code part mapping to the argument at the call-site.)

Coursera Programming Languages, Part B 华盛顿大学 Week 2的更多相关文章

  1. Coursera课程 Programming Languages, Part A 总结

    Coursera CSE341: Programming Languages 感谢华盛顿大学 Dan Grossman 老师 以及 Coursera . 碎言碎语 这只是 Programming La ...

  2. Coursera课程 Programming Languages 总结

    课程 Programming Languages, Part A Programming Languages, Part B Programming Languages, Part C CSE341: ...

  3. Coursera课程 Programming Languages, Part B 总结

    Programming Languages, Part A Programming Languages, Part B Part A 笔记 碎言碎语 很多没有写过 Lisp 程序的人都会对 Lisp ...

  4. The history of programming languages.(transshipment) + Personal understanding and prediction

    To finish this week's homework that introduce the history of programming languages , I surf the inte ...

  5. Natural language style method declaration and usages in programming languages

    More descriptive way to declare and use a method in programming languages At present, in most progra ...

  6. The future of programming languages

    In this video from JAOO Aarhus 2008 Anders Hejlsberg takes a look at the future of programming langu ...

  7. Hex Dump In Many Programming Languages

    Hex Dump In Many Programming Languages See also: ArraySumInManyProgrammingLanguages, CounterInManyPr ...

  8. 在西雅图华盛顿大学 (University of Washington) 就读是怎样一番体验?

    http://www.zhihu.com/question/20811431   先说学校.优点: 如果你是个文青/装逼犯,你来对地方了.连绵不断的雨水会一下子让写诗的感觉将你充满. 美丽的校园.尤其 ...

  9. ESSENTIALS OF PROGRAMMING LANGUAGES (THIRD EDITION) :编程语言的本质 —— (一)

    # Foreword> # 序 This book brings you face-to-face with the most fundamental idea in computer prog ...

  10. Comparison of programming languages

    The following table compares general and technical information for a selection of commonly used prog ...

随机推荐

  1. 第四章:用Python对用户的评论数据进行情感倾向分析

    文章目录 项目背景 获取数据 情感倾向 senta_bilstm 模型 情感划分 数据描述 数据分析 总体评论倾向 评论分布 各分布的情感倾向 评论分词 去除停用词 绘制词云图 结论 源码地址 本文可 ...

  2. 记录安装perl-Verilog过程

    开始,编译带Verilog::Netlist的脚本,报 YumRepo Error: All mirror URLs are not using ftp, http[s] or file.centos ...

  3. uniapp引入微信小程序自定义视频组件--记录

    官方文档:https://zh.uniapp.dcloud.io/tutorial/miniprogram-subject.html 在 pages.json同级目录下 创建目录和文件: wxcomp ...

  4. 最简spring IOC实现

    public class Main { public static void main(String[] args) throws Exception { Class<Address> a ...

  5. win10彻底关闭系统更新 - 禁用 Windows Update Medic Service服务(1803和1809版本)

      这几天有点苦恼呀,不,应该说是挺苦恼,我的新本儿到了几天之后,就开始推荐更新,可以我从心里就不想着更新,那么就要阻止它了,按照原来的办法,进行了双重阻止,这在之前是屡试不爽的,但是还是一直在提醒我 ...

  6. 使用 FreeSSL 申请免费证书

    官网 https://freessl.cn/ 首先,注册一个账户 然后登录 输入自己的域名,选择第2个"亚洲诚信"(1年),然后点击"创建免费SSL证书"按钮 ...

  7. P5737 闰年展示

    快乐的题目穿梭机 这道题目其实so easy啦,还是先来顺一遍流程: 输入数据 列举出x到y区间内的年份 判断是否是闰年 是闰年cnt(或sum)计数器++ 输出计数器和年份 闰年知识补充站 1.能被 ...

  8. QT4.8.6移植

    sudo apt-get install libx11-dev libxext-dev libxtst-dev 配置: ./configure --prefix=/opt/qt4.8.6 -opens ...

  9. 读后笔记 -- Python 全栈测试开发 Chapter7:移动自动化测试框架

    7.1 主流框架优缺点剖析 1. APP 主要测试策略 安装.卸载测试: 安装:1)安装路径:2)安装环境(平台.安全软件):3)安全权限(获取位置.摄像头.通讯录.ROOT管理员等权限):4)安装的 ...

  10. Python使用pip安装第三方模块问题

    安装时总会出现错误:pip is configured with locations that require TLS/SSL, however the ssl module in Python is ...