Coursera Programming Languages, Part B 华盛顿大学 Week 2
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 的 expression, subexpression 以及 let-expression 的 body 部分三种情况
Implementing closures
除了变量之外,一个编程语言中必不可少的部分还有函数
回忆之前关于 function closure 与 lexical 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的更多相关文章
- Coursera课程 Programming Languages, Part A 总结
Coursera CSE341: Programming Languages 感谢华盛顿大学 Dan Grossman 老师 以及 Coursera . 碎言碎语 这只是 Programming La ...
- Coursera课程 Programming Languages 总结
课程 Programming Languages, Part A Programming Languages, Part B Programming Languages, Part C CSE341: ...
- Coursera课程 Programming Languages, Part B 总结
Programming Languages, Part A Programming Languages, Part B Part A 笔记 碎言碎语 很多没有写过 Lisp 程序的人都会对 Lisp ...
- 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 ...
- 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 ...
- The future of programming languages
In this video from JAOO Aarhus 2008 Anders Hejlsberg takes a look at the future of programming langu ...
- Hex Dump In Many Programming Languages
Hex Dump In Many Programming Languages See also: ArraySumInManyProgrammingLanguages, CounterInManyPr ...
- 在西雅图华盛顿大学 (University of Washington) 就读是怎样一番体验?
http://www.zhihu.com/question/20811431 先说学校.优点: 如果你是个文青/装逼犯,你来对地方了.连绵不断的雨水会一下子让写诗的感觉将你充满. 美丽的校园.尤其 ...
- ESSENTIALS OF PROGRAMMING LANGUAGES (THIRD EDITION) :编程语言的本质 —— (一)
# Foreword> # 序 This book brings you face-to-face with the most fundamental idea in computer prog ...
- Comparison of programming languages
The following table compares general and technical information for a selection of commonly used prog ...
随机推荐
- #pragma 小节
(1)#pragma startup function-name <priority> (2)#pragma exit function-name <priority> 含 ...
- KiCad,一款开源的PCB设计软件
Kicad,一款开源的PCB设计软件 1.偷偷摸摸久矣 还记得是大二的实践课,用Protel99se设计一款PCB,把电路图转印到铜板上并腐蚀出来,然后钻孔.焊接,那时候的软件是真难用,后面自学了Al ...
- Elasticsearch 实战
需求 假设现在有这么一个需求,系统接了很多的报文,需要提供全文检索,为了简化,报文目前只有类型,流水号,内容这三个字段. 索引设计 建立msg索引,映射规则如下 PUT /msg { "ma ...
- C#中socket的简单使用
一.Socket的概念Socket其实并不是一个协议,而是为了方便使用TCP或UDP而抽象出来的一层,是位于应用层和传输控制层之间的一组接口. 当两台主机通信是,必须通过Socket连接,Socket ...
- (一)从路由器和IP地址开始折腾
我们应当知道的一点是,由于IP地址只有32bit, 所以很快就面临着不够用的情况,现在之所以大家还在正常使用IPv4, 就是因为采用了公有地址和私有地址的概念:所谓的私有地址是从当时公有地址中还没有分 ...
- jabc连接数据库
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法.JD ...
- jmeter-时间处理
${__time(,)} 1486091280955 //无格式化参数,返回当前毫秒时间,默认13位.一般用来做时间戳 ${__time(/1000,)} //为取10位的时间戳的函数表达式(时间精确 ...
- gin web 2
routers/router.go package routers import ( "github.com/gin-gonic/gin" "gin-blog/pkg/s ...
- Java设计模式之策略模式(13)
策略模式定义了一系列算法,每个算法封装起来,他们可以相互替换,且算法的变化不会影响到使用算法的客户.可以设计一个抽象类提供辅助. package WHP; public interface ICalc ...
- C++ NRV优化
NRV,named return value 某个函数返回一个对象时,在外部接收 A a = Fun(); 会调用复制构造函数 会损失性能 为何不能将函数Fun定义成传入引用a,然后直接在a上进行操作 ...