lambda calculus语言解释器

    看到王垠的这篇博文《怎样写一个解释器》,里面简单介绍了lambda calculus语言及解释器,即简单又抓住了解释器的实质。

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; 以下三个定义 env0, ext-env, lookup 是对环境(environment)的基本操作:

;; 空环境
(define env0 '())

;; 扩展。对环境 env 进行扩展,把 x 映射到 v,得到一个新的环境
(define ext-env
  (lambda (x v env)
    (cons `(,x . ,v) env)))

;; 查找。在环境中 env 中查找 x 的值
(define lookup
  (lambda (x env)
    (let ([p (assq x env)])
      (cond
       [(not p) x]
       [else (cdr p)]))))

;; 闭包的数据结构定义,包含一个函数定义 f 和它定义时所在的环境
(struct Closure (f env))

;; 解释器的递归定义(接受两个参数,表达式 exp 和环境 env)
;; 共 5 种情况(变量,函数,调用,数字,算术表达式)
(define interp1
  (lambda (exp env)
    (match exp                                          ; 模式匹配 exp 的以下情况(分支)
      [(? symbol? x) (lookup x env)]                    ; 变量
      [(? number? x) x]                                 ; 数字
      [`(lambda (,x) ,e)                                ; 函数
       (Closure exp env)]
      [`(,e1 ,e2)                                       ; 调用
       (let ([v1 (interp1 e1 env)]
             [v2 (interp1 e2 env)])
         (match v1
           [(Closure `(lambda (,x) ,e) env1)
            (interp1 e (ext-env x v2 env1))]))]
      [`(,op ,e1 ,e2)                                   ; 算术表达式
       (let ([v1 (interp1 e1 env)]
             [v2 (interp1 e2 env)])
         (match op
           ['+ (+ v1 v2)]
           ['- (- v1 v2)]
           ['* (* v1 v2)]
           ['/ (/ v1 v2)]))])))

;; 解释器的“用户界面”函数。它把 interp1 包装起来,掩盖第二个参数,初始值为 env0
(define interp
  (lambda (exp)
    (interp1 exp env0)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    这个短小的程序完成了任何解释器的核心功能,实现了语言的三大基本元素:变量、函数和调用。

    解释器接收的输入是“程序”,输出的是对程序的解释执行结果。实际上各种程序语言在经过语法分析之后,得到的是“抽象语法树”。

    如果我们直接绕过语法分析,或者简化语法,直指抽象语法树,那么程序处理将变得非常容易。就像在自然语音领域,我们的最终目的不是语言本身,而是语义,它代表我们真正要传达的信息。现在演讲时经常使用幻灯片,通过图片,声音,逻辑图等等方式来传达信息,就是因为自然语言的语法及其表达能力有限,需要改进。改进后的方式,可能一幅设计良好的示意图,要比洋洋洒洒好几千字的文章还要清晰明了。而scheme的S表达式其实是一种语法树,当初设计时是想让它作为中间语言,最后觉得用起来非常方便而且直接,就作为scheme的表达式,由于它接近语法树,所以自身的语法规则就非常简单,因为需要被处理的步骤少。

    首先定义了三个关于环境的过程,env0为空环境,ext-env扩展环境,lookup查找环境中的变量值。环境是用来保存变量的值的地方。变量呢,在这里就是函数的输入,它的值是可变的,被绑定不同数字就表示不同的值,而变量—值作为一对(pair)就保存在该函数对应的环境中。

    还定义了闭包的概念,利用struct Closure保存函数表达式及其环境,这对于函数嵌套定义时有相同变量名的处理很有用,解决了名称决议(name resolution)的问题。避免了dynamic scope带来的变量名引用困扰。

(interpl '((lambda (x) (* 2 x)) 3))

这个代码是调用刚才定义的解释器,输入的表达式为'((lambda (x) (* 2 x)) 3)。根据解释器代码分析模式匹配一个调用(,e1 ,e2),然后let语句表示递归调用自身求值e1,e2并分别绑定到v1,v2。e1为(lambda (x) (* 2 x)),解释器通过闭包将这个lambda表达式与它的环境env一起记录下来,然后match v1处理这个闭包,将lambda的参数x及其值v2(等于数字3)扩展到环境中。扩展后的环境将作为参数,连同lambda函数体(* 2 3)一起传递给解释器求值。这里x变量,其值就保存在刚才传递给解释器的环境中(通过lookup接口查询env)。最后调用计算机器的match pattern得到6。如果遇到lambda嵌套定义函数时,尤其是参数名还相同的情况下,为使程序行为可预测,闭包和环境的概念就起了很大作用了。

(interp '(((lambda (x) (lambda (y) (* x y))) 2) 3))
;; => 6

(interp '((lambda (x) (* 2 x)) 3))
;; => 6

(interp '((lambda (y) (((lambda (y) (lambda (x) (* y 2))) 3) 0)) 4))


你可能感兴趣的:(lambda calculus语言解释器)