Coursera Programming Languages, Part B 华盛顿大学 Homework 5
这次 Week 2 的作业比较难,任务目标是使用 \(racket\) 给一个虚拟语言 \(MUPL\) (made-up programming language) 写一个解释器
所以单独开个贴来好好分析一下
首先是 MUPL 语言的几个 semantic,已经通过 \(racket\) struct 的形式提供
(struct var (string) #:transparent) ;; a variable, e.g., (var "foo")
(struct int (num) #:transparent) ;; a constant number, e.g., (int 17)
(struct add (e1 e2) #:transparent) ;; add two expressions
(struct ifgreater (e1 e2 e3 e4) #:transparent) ;; if e1 > e2 then e3 else e4
(struct fun (nameopt formal body) #:transparent) ;; a recursive(?) 1-argument function
(struct call (funexp actual) #:transparent) ;; function call
(struct mlet (var e body) #:transparent) ;; a local binding (let var = e in body)
(struct apair (e1 e2) #:transparent) ;; make a new pair
(struct fst (e) #:transparent) ;; get first part of a pair
(struct snd (e) #:transparent) ;; get second part of a pair
(struct aunit () #:transparent) ;; unit value -- good for ending a list
(struct isaunit (e) #:transparent) ;; evaluate to 1 if e is unit else 0
;; a closure is not in "source" programs but /is/ a MUPL value; it is what functions evaluate to
(struct closure (env fun) #:transparent)
我们用 eval-exp
函数对上面的 semantic 进行解释
(define (eval-exp e)
(eval-under-env e null))
而 eval-under-env
函数则是在提供的 \(env\) 环境下对表达 \(e\) 进行解释,也是我们需要实现的主要函数
这里,环境 我们用 \(racket\) pair 组成的 list 表示,每个 (string, val)
组成的 pair 中,string
为变量名,而 val
为变量值
另外作业中还提供了 envlookup
函数,提供变量名 string
,返回环境中对应的变量值
(define (envlookup env str)
(cond [(null? env) (error "unbound variable during evaluation" str)]
[(equal? (car (car env)) str) (cdr (car env))]
[#t (envlookup (cdr env) str)]))
在开始分析之前,需要强调一下 \(MUPL\) 语言是一门函数式语言,一个表达 (expression) 是由若干个表达 (subexpression) 嵌套而成
在所有提供的 semantic 中,可以产生 binding 的只有 mlet
(let 表达定义的本地变量 binding),call
(call 中传入的值 call-actual
与对应函数的参数 fun-formal
进行 binding) 以及 fun
(函数名 fun-nameopt
与函数)
接下来我们一个一个分析对应的 case:
- \(var\)
var
中存储的是一个变量的名字,对其解释的结果应该是这个变量的值,所以
[(var? e) (envlookup env (var-string e))]
- \(int\), \(aunit\) 与 \(closure\)
对于 int
,其储存的是一个 int 类型的数字,可以算作是所谓最基本的表达,无需再进行解释
aunit
与 closure
类似。如果说 int 是 int 类型变量的值,那么 closure 可以说是 function 的 值。所以
[(int? e) e]
[(aunit? e) e]
[(closure? e) e]
- \(fun\) 与 \(call\)
这里真的想了很久,关于 \(fun\) 与 \(call\) 两个表达的解释方式
由于受到线性语言的影响,自然而然会有这样的想法:在函数被定义的时候将其与对应的 closure 加入环境 (形成一个 fun-closure binding),在被调用的时候像变量一样进行解释
这样的想法是可行的,但在 \(MUPL\) 语言中,由于只提供了有限的 semantic,我们可以发现类似 函数定义形成 binding (如 ML 的 fun
, Racket 的 define
) 是没有对应的 semantic 的,唯一能使函数形成 binding 的地方 在 \(call\) 语句中 (在解释函数体时将 fun-closure binding 加入环境以实现函数的递归调用)
本质的来讲,就是 \(MUPL\) 语言中,函数的 第一次被定义与第一次(层)被调用是同步进行的。所以,只有第二层及以上的调用可以使用函数的名字
而在线性语言中,函数的定义和调用之间可以间隔很远,在定义时解释器就将 fun-closure binding 加入环境,之后的调用都可以直接采用名字来调用
[(fun? e) (closure env e)] ; closure is function's value
[(call? e)
(let ([cl (eval-under-env (call-funexp e) env)]
[arg (eval-under-env (call-actual e) env)])
(if (closure? cl)
(let* ([fn (closure-fun cl)]
[bodyenv (cons (cons (fun-formal fn) arg) (closure-env cl))] ; 将 参数名-参数值对 传入解释函数体的环境中
[bodyenv (if (fun-nameopt fn) (cons (cons (fun-nameopt fn) cl) bodyenv) bodyenv]) ; 若函数具名,则将 函数-闭包对 传入解释函数体的环境,以保证递归可实现
(eval-under-env (fun-body fn) bodyenv)) ; 解释函数体的环境 = 定义函数的环境 (存储在闭包中) + 参数相关绑定 + 函数-闭包绑定
(error "MUPL funciton call with nonfunction")))]
- \(add\) 与 \(ifgreater\)
这个就比较简单了,比较标准的树形结构,先 interpret subexpressions 再最终 interpret expression
贴一个 add
,ifgreater
差不多
[(add? e)
(let ([v1 (eval-under-env (add-e1 e) env)]
[v2 (eval-under-env (add-e2 e) env)])
(if (and (int? v1)
(int? v2))
(int (+ (int-num v1)
(int-num v2)))
(error "MUPL addition applied to non-number")))]
- \(apair\),\(fsd\) 与 \(snd\)
三个 \(pair\) 相关的 semantic,同样遵循 subexpression-expression 的规则
[(apair? e)
(apair (eval-under-env (apair-e1 e) env) (eval-under-env (apair-e2 e) env))]
[(fst? e)
(let ([pr (eval-under-env (fst-e e) env)])
(if (apair? pr)
(apair-e1 pr)
(error "MUPL fst applied to non-apair")))]
[(snd? e)
(let ([pr (eval-under-env (snd-e e) env)])
(if (apair? pr)
(apair-e1 pr)
(error "MUPL snd applied to non-apair")))]
- \(isaunit\)
aunit
这个 semantic 很特殊,它没有 field,因此也储存不了任何其他值
它的存在和 ML 中的 NONE 很相似
[(isaunit? e)
(let ([v (eval-under-env (isaunit-e e) env)])
(if (aunit? v) (int 1) (int 0)))]
- \(mlet\)
call
与 mlet
是唯二可以向环境中添加新 binding 的 semantic
所以只要理解了 \(call\),\(mlet\) 的实现也可以说是很简单
[(mlet? e)
(let ([bodyenv (cons (cons (mlet-var e) (eval-under-env (mlet-e e) env)) env)])
(eval-under-env (mlet-body e) bodyenv))]
至此,我们已经成功编写了 \(MUPL\) 语言的解释器 eval-exp
接下来,我们要用 racket 继续 扩展 这门语言
扩展一门语言的方法就是:用元语言编写函数的方式来定义宏 (Defining "Macros" via functions in metalanguage)
记得一定要分清被实现语言 (language being implemented) 与元语言 (metalanguage) 的区别
也就是说,在用编写函数的方式来定义被实现语言的宏时,不能用到元语言本身的 semantic (包括 eval-exp
)
(ifaunit e1 e2 e3)
若 \(e1\) evaluate 为 aunit,则返回 \(e2\),否则返回 \(e3\)
; 错误例子1
(define (ifaunit e1 e2 e3)
(if (aunit? e1) e2 e3)) ; 这样定义的 macro 是错误的,因为用到了元语言的 semantic "if"
; 正确写法
(define (ifaunit e1 e2 e3)
(ifgreater (isaunit e1) (int 0) e2 e3)) ; 这里用到的所有 semantic (ifgreater, isaunit) 都是 MUPL 语言中的
(mlet* bs e2)
初始环境为空,按顺序 evaluate bs 中的每一个 (var-val) 对并添加入环境,最后用该环境 evaluate e2
(define (mlet* bs e2)
(ifaunit bs
e2
(mlet (fst (fst bs)) (snd (fst bs)) (mlet* (snd bs)))))
(ifeq e1 e2 e3 e4)
若 \(e1\) 与 \(e2\) evaluate 出的值一致,则返回 \(e3\),否则返回 \(e4\)
且保证 \(e1\) 与 \(e2\) 只被 evaluate 一次
(define (ifeq e1 e2 e3 e4)
(mlet "_x" e1
(mlet "_y" e2 ; 定义临时变量,保证 e1 与 e2 都只被 evaluate 一次
(ifgreater (var "_x") (var "_y") e4
(ifgreater (var "_y") (var "_x") e4 e3)))))
mupl-map
将名为 mupl-map
的 \(Racket\) 函数与一个 \(MUPL\) 函数绑定
这个函数 takes 一个函数 \(a\),返回一个函数 \(b\)
函数 \(b\) takes 一个 list,并且将 \(a\) 对 list 的每个元素应用,最后返回新 list (其实就是 map)
(define mupl-map
(fun #f "a"
(fun "b" "ls"
(ifaunit (var "ls")
(aunit)
(apair (call (var "a") (fst (var "ls")))
(call (var "b") (snd (var "ls"))))))))
注意,在调用函数时,第一个参数 funexp
是整个函数本身,所以我们用 (var "fun_name")
进行传入
这样 evaluate 出来的结果也是对应的 closure,符合 evaluate function 后得到的结果
mupl-mapAddN
同上,函数 takes 一个整数 \(i\) 返回一个函数 \(f\)
这个函数 \(f\) takes 一个 list,并将 list 中的每个元素都加上 \(i\)
(define mupl-map
(fun #f "i"
(call mupl-map (fun #f "x" (add (var "x") (var "i"))))))
Coursera Programming Languages, Part B 华盛顿大学 Homework 5的更多相关文章
- 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 ...
随机推荐
- Oralyzer-20220205
Usage: oralyzer.py [-h] [-u URL] [-l PATH] [-crlf] [-p PAYLOAD] [--proxy] [--wayback] 可选参数: -h, --he ...
- [2015年NOIP提高组] 跳石头
一年一度的"跳石头"比赛又要开始了! 这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石.组委会已经选择好了两块岩石作为比赛起点和终点.在起点和终点之间,有 <spa ...
- python读取word文档,插入mysql数据库实例
表格内容如下: 1.实现批量导入word文档,取文档标题中的数字作为编号 2.除取上面打钩的内容需要匹配出来入库入库,其他内容全部直接入库mysql # wuyanfeng# -*- coding:u ...
- lombok安装不了的问题
- 12组-Beta冲刺-5/5
一.基本情况 队名:字节不跳动 组长博客:https://www.cnblogs.com/147258369k/p/15609352.html Github链接:https://github.com/ ...
- Python学习笔记(四)算术运算符
一.算术运算符 运算符 说明 实例 结果 + 加 12.45 + 15 27.45 - 减 4.56 - 0.26 4.3 * 乘 5 * 3.6 18.0 / 除法(和数学中的规则一样) 7 / 2 ...
- 转载-Shell脚本中字符串截取功能
在Shell脚本编写中,有几个地方都是要用到字符串截取的功能,那将这块的内容进行下记录: 1.字符串变量的截取操作 对字符串变量的截取操作一般都是通过${操作符}的方式进行 1)从指定位置index截 ...
- satpy 处理卫星 FY4A 数据
读取数据并画图 import os import glob from datetime import datetime, timedelta from satpy.scene import Scene ...
- gitee上传VS2022已有项目
1.在gitee上新建仓库: 2.复制新建仓库地址: 3.用VS2022打开先有项目,找到Git更改项: 4.点击创建Git存储库: 5.创建本地仓库并推送到远程,点击创建并推送: 6.等待创建成功即 ...
- Py_excel
py_excel xlrd 读excel workbook = xlrd.open_workbook(file_path) sheet = workbook.sheet_by_name(sheet_n ...