这次 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:

  1. \(var\)

var 中存储的是一个变量的名字,对其解释的结果应该是这个变量的值,所以

[(var? e) (envlookup env (var-string e))]
  1. \(int\), \(aunit\) 与 \(closure\)

对于 int,其储存的是一个 int 类型的数字,可以算作是所谓最基本的表达,无需再进行解释

aunitclosure 类似。如果说 int 是 int 类型变量的,那么 closure 可以说是 function 的 。所以

[(int? e) e]
[(aunit? e) e]
[(closure? e) e]
  1. \(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")))]
  1. \(add\) 与 \(ifgreater\)

这个就比较简单了,比较标准的树形结构,先 interpret subexpressions 再最终 interpret expression

贴一个 addifgreater 差不多

[(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")))]
  1. \(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")))]
  1. \(isaunit\)

aunit 这个 semantic 很特殊,它没有 field,因此也储存不了任何其他值

它的存在和 ML 中的 NONE 很相似

[(isaunit? e)
(let ([v (eval-under-env (isaunit-e e) env)])
(if (aunit? v) (int 1) (int 0)))]
  1. \(mlet\)

callmlet 是唯二可以向环境中添加新 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)

  1. (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 语言中的
  1. (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)))))
  1. (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)))))
  1. 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 后得到的结果

  1. 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的更多相关文章

  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. 主页面调取iframe子页面的子页面数据

    iframe = this.iframe.contentWindow; var rowsData = iframe.$("#sonList2")[0].contentWindow. ...

  2. CentOS下下查看硬盘型号、大小等信息(含Raid)

    一.普通模式(该机硬盘没有做磁盘阵列) 1.fdisk -l 查看你的硬盘编号,如sda,sdb 等 2.smartctl --all /dev/sda [html] view plain copy ...

  3. [OC] Block 是什么

    Block 是 带有自动变量的匿名函数.  emmmm 反正我看了也不懂,我们一点点用具体的例子分析一下block. 1. 我们现在要在一个文件中使用block(我们后面再提到两个界面传值的block ...

  4. [服务器]Windows Server 2008 64位1核1G安装SQL Server2008

    本文为原创文章,转载请注明出处!我的博客地址:http://www.cnblogs.com/txwd 这台服务器是2017年初买的,那时候1核1G,3年要1500.不过现在便宜了,2核4G,3年也就1 ...

  5. Quartz 2D CGGradient与CGShading实现渐变的绘制

    Quartz 提供了两种不透明的数据类型来创建渐变CGShading 和 CGGradient,你可以使用其中任何一个来创建轴向或径向渐变. 轴向渐变:沿着一个轴方向线性渐变 径向渐变:一个点为原型, ...

  6. 【AD21】软件基础

    1.AD21最后生成什么文件打板子? 生成gerber文件 2.一个工程包括什么? 原理图库,原理图,PCB库,PCB 3.距离单位? 默认是mil,1mm约为40mil 4.软件中PCB层? 表层焊 ...

  7. 「部署日记」Android Studio乱码解决方案

    弄了一台新电脑,第一件事肯定是弄好打造台啦 于是VS.AS.CRD.NSIS.Adobe全家桶全安装完毕, 问题来了,在打开Android Studio时,出现乱码,比如 这样的: 这样的: 这样的: ...

  8. UI动画 - CATransaction

    前言 1 - CAAnimation 并不是一个单纯的实现动画的框架,它原本叫 Layer Kit.管理着树状结构的图层数据,并快速组合这些图层,最终构成了一切可视化的基础 2 - 在构建可视化,也就 ...

  9. ts的装饰器

    console.log('装饰器.......') // 装饰器就是一个方法,可以注入到类,方法,属性上来拓展类,属性,方法,参数的功能 // 常见:类装饰器,属性装饰器,方法装饰器,参数装饰器 // ...

  10. R语言主成分PCA、因子分析、聚类对地区经济研究分析重庆市经济指标|附代码数据

    全文下载链接:http://tecdat.cn/?p=27515 最*我们被客户要求撰写关于主成分PCA.因子分析.聚类的研究报告,包括一些图形和统计输出. 建立重庆市经济指标发展体系,以重庆市一小时 ...