《EOPL》学习之解释器的不同写法对比。尾递归和cont的原理。

interp

注意,不带cont的a的轻

如果是val是常数const

ex.不带cont的情况,也不带状态state,即无法赋值,传入形式是*(lambda (exp env)*

(const-exp (num) (num-val num))

a. 不带cont的情况,传入形式是*(lambda (exp env)*

(const-exp (num) (num-val num))

b. 带cont的情况,传入形式是*(lambda (exp env cont)*

(const-exp (num) (apply-cont cont (num-val num)))

同时,apply-cont的写法无:
因为根据cont来分,一般num的情况不会引发新的cont。

c.带cont和registers的情况,传入形式是*(lambda ()*

 (const-exp (num)
 (set! val (num-val num))--赋值了,然后就移交给apply-cont去管
 --cont is unchanged          
 (apply-cont))

同时,apply-cont的写法无:
因为根据cont来分,一般num的情况不会引发新的cont。

如果是val是符号(标记符、或称形参)

ex.不带cont的情况,也不带状态state,即无法赋值,传入形式是*(lambda (exp env)*

(var-exp (var) (apply-env env var))

a. 不带cont的情况,

(var-exp (var) (deref (apply-env env var))) --其中deref表示取得后面表达式表示的那个name的content

b.带cont的情况

(var-exp (var) (apply-cont cont (apply-env env var)))

同时,apply-cont的写法无。
c.带cont和registers的情况

(var-exp (var)
-- (apply-cont cont (apply-env env id)))                 
(set! val (apply-env env var))
-- cont is unchanged          
(apply-cont))

同时,apply-cont的写法无。

如果是val是diff-exp

ex.不带cont的情况,也不带状态state,即无法赋值

  (diff-exp (exp1 exp2)
            (let ((val1 (value-of exp1 env))
                  (val2 (value-of exp2 env)))
              (let ((num1 (expval->num val1))
                    (num2 (expval->num val2)))
                (num-val
                 (- num1 num2))))

a. 不带cont的情况,
call by ref,但这里没有体现。遇到这种算式直接计算出num-val的结果了。里面保证了value-of其中的表达式的值肯定是num,不然要报错。应该是一个树枝。

  (diff-exp (exp1 exp2)
            (let ((val1 (value-of exp1 env))
                  (val2 (value-of exp2 env)))
              (let ((num1 (expval->num val1))
                    (num2 (expval->num val2)))
                (num-val
                 (- num1 num2)))))

b.带cont的情况
对cont进行了改造,表示了下一步的情况。要求下一步对cont要做一个处理。

  (diff-exp (exp1 exp2)
            (value-of/k exp1 env
                        (diff1-cont exp2 env cont)))     

apply-cont针对diff1-cont有一个处理:
即对传过来的exp2也进行包裹,并把要对exp2做的事情包裹在diff2-cont中。这是因为diff有两个参数。

  (diff1-cont (exp2 saved-env saved-cont)
              (value-of/k exp2
                          saved-env (diff2-cont val saved-cont)))

c.带cont和registers的情况

  (diff-exp (exp1 exp2)
            --(value-of/k exp1 env (diff1-cont exp2 env cont))              
            (set! cont (diff1-cont exp2 env cont))
            (set! exp exp1)
            -- env is unchanged          
            (value-of/k))

apply-cont针对diff1-cont有一个处理:
这里把exp的值的内容写成了exp2。

  (diff1-cont (exp2 saved-env saved-cont)
              ;--(value-of/k exp2 env (diff2-cont val cont)))
              (set! cont (diff2-cont val saved-cont))
              (set! exp exp2)
              (set! env saved-env)
              (value-of/k))

从value_of/k一般是根据exp表达式来选择处理分支的,而apply-cont是根据cont的表达式来选择处理分支的。
上面的ex和a都直接返回了值,而b会要求value_of/k对exp1进行解析。解析完毕后,理论上会得到一个值。这个值到时候要干嘛,是根据当时的cont来决定的。
我先列出b和c的diff2-cont,因为之后也许不会列出了。cont的表达式明显和value_of/k不是一一对应的,cont有自己的情境。

在b中,最终算出了这个值,并要求继续apply-cont来计算,缩小上下文,而不是增长cont的内容。

  (diff2-cont (val1 saved-cont)
              (let ((num1 (expval->num val1))
                    (num2 (expval->num val)))
                (apply-cont saved-cont
                            (num-val (- num1 num2)))))

在c中,把值存入val,并把当前上下文给cont。这样会根据cont对新求出的值进行处理。

  (diff2-cont (val1 saved-cont)
              ;; --(apply-cont cont (num-val (- num1 num2)))))
              (let ((num1 (expval->num val1))
                    (num2 (expval->num val)))
                (set! cont saved-cont)
                (set! val (num-val (- num1 num2)))
                (apply-cont)))

如果是val是zero?-exp

ex:
递归算出表达式的值来,然后将值变成value,判断value,然后返回一个bool-val的表达式。

  (zero?-exp (exp1)
             (let ((val1 (value-of exp1 env)))
               (let ((num1 (expval->num val1)))
                 (if (zero? num1)
                     (bool-val #t)
                     (bool-val #f)))))

a:

  (zero?-exp (exp1)
             (let ((val1 (value-of exp1 env)))
               (let ((num1 (expval->num val1)))
                 (if (zero? num1)
                     (bool-val #t)
                     (bool-val #f)))))

b:
判断是否是zero要分为两步,由此,我们需要两个zero的上下文步骤。首先先求值。

  (zero?-exp (exp1)
             (value-of/k exp1 env
                         (zero1-cont cont)))

对于zero1,apply-cont那边的写法是:

  (zero1-cont (saved-cont)
              (apply-cont saved-cont
                          (bool-val
                           (zero? (expval->num val)))))

就不增加cont了,读取下一个cont,并给有一个bool-val值。

c.

  (zero?-exp (exp1)
             -- (value-of/k exp1 env (zero1-cont cont))
             (set! cont (zero1-cont cont))
             (set! exp exp1)
             (value-of/k))

对于zero1,apply-cont那边的写法是:

  (zero1-cont (saved-cont)
              ;;-- (apply-cont cont
              ;;--   (bool-val
              ;;--     (zero? (expval->num val))))
              (set! cont saved-cont)
              (set! val (bool-val (zero? (expval->num val))))
              (apply-cont))

如果是val是if-exp

ex:
获得一个bool值,并非根据bool值返回接下来要走那一条路。

  (if-exp (exp1 exp2 exp3)
          (let ((val1 (value-of exp1 env)))
            (if (expval->bool val1)
                (value-of exp2 env)
                (value-of exp3 env))))

a:
与ex相同

b:
上下文要走两次,因为也要做2件事。

 (if-exp (exp1 exp2 exp3)
              (value-of/k exp1 env
                          (if-test-cont exp2 exp3 env cont)))

apply-cont:

  (if-test-cont (exp2 exp3 saved-env saved-cont)
                (if (expval->bool val)
                    (value-of/k exp2 saved-env saved-cont)
                    (value-of/k exp3 saved-env saved-cont)))

c:

  (if-exp (exp1 exp2 exp3)
          ;; (value-of/k exp0 env (if-test-cont exp2 exp3 env cont))
          (set! cont (if-test-cont exp2 exp3 env cont))
          (set! exp exp1)
          (value-of/k))

apply-cont:

  (if-test-cont (exp2 exp3 saved-env saved-cont)
                (set! cont saved-cont)
                (if (expval->bool val)
                    (set! exp exp2)
                    (set! exp exp3))
                (set! env saved-env)
                (value-of/k))

最后一步不是apply-cont,而是value-of/k。diff的第一步也是这样。

如果val是let

ex:
一个树枝,求值exp1,然后得到的值和符号var关联在一起(扩展环境)。同时,直接求值处理let后面的body部分的内容。就是接在in后面的那部分内容。

  (let-exp (var exp1 body)       
           (let ((val1 (value-of exp1 env)))
             (value-of body
                       (extend-env var val1 env))))

a:
类似,但是v1作为一个表达式exp1的求值结果,newref表示把v1存入了这个地方,并把这个地方的那个name告知了var。
环境部分的函数的写法可以参考:
https://github.com/racket/eopl/blob/master/tests/chapter4/implicit-refs/environments.rkt

  (let-exp (var exp1 body)       
           (let ((v1 (value-of exp1 env)))
             (value-of body
                       (extend-env var (newref v1) env))))

b:
让exp1进行求值,对于cont的话,增加,增加了一个要赋值的上下文。这个上下文将取得最后得到的那个val的值,也就是这个函数value-of/k的结果。

  (let-exp (var exp1 body)
           (value-of/k exp1 env
                       (let-exp-cont var body env cont)))

针对下一步的apply-cont:

  (let-exp-cont (var body saved-env saved-cont)
                (value-of/k body
                            (extend-env var val saved-env) saved-cont))

c:

  (let-exp (var exp1 body) 
           ;;-- (value-of/k rhs env (let-exp-cont id body env cont)) 
           (set! cont (let-exp-cont var body env cont))
           (set! exp exp1)
           (value-of/k))

apply-cont:

  (let-exp-cont (var body saved-env saved-cont)
                ;;-- (value-of/k body (extend-env id val env) cont)                     
                (set! cont saved-cont)
                (set! exp body)
                (set! env (extend-env var val saved-env))
                (value-of/k))

如果val是proc-exp

ex:
其实不用做太多,直接变成proc-val等待处理。话说这个procedure其实也是一个标识符,用于把一个数据结构,比如列表中的某位的表达式当作是一个调用其他值的函数,就是用于标识一个proc结构的。一个proc结构里会有一个procedure,类似(procedure ba la ba )这样的。
由此也可以知道,procedure如果是一个构造函数,它会取两个值(body env),然后再取一个值(val ,也就是实参)。
而proc如果是一个构造函数,它会只取一个val,因为它自己已经包含了procedure,所以直接把procedure用在val上即可。

  (proc-exp (var body)
            (proc-val (procedure var body env)))
            

a:
同上

b:

  (proc-exp (var body)
            (apply-cont cont 
                        (proc-val (procedure var body env))))

有趣的是,不仅没有新的上下文产生,而且出现了一个之前没有见过的结构,即这个proc-val其实并不能在apply-cont中找到,而如果cont没有的话,会直接去取value的值。而这个val明显肯定可以在后续被传入一个val,然后继续下去。这刚好达到了实参进入的效果。
当然,以上只是猜测,还是慢慢验证比较好,比如
proc-val是什么?它是一个便捷的写法表示,表示后面是一个proc。
如果遇到了真正的proc,其实是遇到了一个call-exp。这个结构肯定是内嵌在这个里面的。
所以我们接下来就说call-exp。
c:
啊!看了下面这个就能明白了。apply-cont只是继续下去了。cont找到最近的cont来处理,然后会用到需要被求值的val。这说明proc-val只是一个求值的过程中的一环,无需多说。

  (proc-exp (var body)
            ;; --(apply-cont cont (proc-val (procedure bvar body env))
            (set! val (proc-val (procedure var body env)))
            (apply-cont))

如果val是call-exp

ex:
树枝。两步多出来的。

  (call-exp (rator rand)
            (let ((proc (expval->proc (value-of rator env)))
                  (arg (value-of rand env)))
              (apply-procedure proc arg)))

附上apply-procedure

(define apply-procedure
  (lambda (proc1 arg)
    (cases proc proc1
      (procedure (var body saved-env)
                 (value-of body (extend-env var arg saved-env))))))

body提前,内里的东西才是最早形成(接近输入文本)的那部分。(闭包的效果),其中var这个形参和arg这个实参进行了绑定(环境扩张)。之后就是求值body的内容了。

a:

和ex一样。
乍一看,apply-procedure部分有很大不同。但其实一样,只是加了一个输出的debug。

(define apply-procedure
  (lambda (proc1 arg)
    (cases proc proc1
      (procedure (var body saved-env)
                 (let ((r (newref arg)))
                   (let ((new-env (extend-env var r saved-env)))
                     (when (instrument-let)
                       (eopl:printf
                        "entering body of proc ~s with env =~%"
                        var)
                       (pretty-print (env->list new-env)) 
                       (eopl:printf "store =~%")
                       (pretty-print (store->readable (get-store-as-list)))
                       (eopl:printf "~%"))
                     (value-of body new-env)))))))  

;; store->readable : Listof(List(Ref,Expval)) 
;;                    -> Listof(List(Ref,Something-Readable))
(define store->readable
  (lambda (l)
    (map
     (lambda (p)
       (list
        (car p)
        (expval->printable (cadr p))))
     l)))

b:
既然有两部多出来的,那肯定会有函数值计算和参数值的计算。分为两步。那么要做的事情就先放入rator_cont中,作为上下文储存起来。等到时间允许起来之后,就其实就是把value-of/k rator env 得到的值,作为放入rator-cont的这个上下文做完之后要做的事情,作为它的参数。

  (call-exp (rator rand) 
            (value-of/k rator env
                        (rator-cont rand env cont)))

apply-cont的写法:
apply-cont的参数一直都是(val 和cont),然后根据cont做一些操作。这里的值,就是后来给予rator_cont这个函数的值。这个值是rator。

  (rator-cont (rand saved-env saved-cont)
              (value-of/k rand saved-env
                          (rand-cont val saved-cont)))

在这里算的时候,就直接获得了函数,然后运用函数。这里的val是rand部分的值,所以刚好是实参。

  (rand-cont (val1 saved-cont)
             (let ((proc (expval->proc val1)))
               (apply-procedure/k proc val saved-cont)))

apply-procedure如下:

(define apply-procedure/k
  (lambda (proc1 arg cont)
    (cases proc proc1
      (procedure (var body saved-env)
                 (value-of/k body
                             (extend-env var arg saved-env)
                             cont)))))

返回的内容差不多,就是把var和arg绑定。然后继续求值body。

c:

  (call-exp (rator rand)
            ;; --(value-of/k rator env (rator-cont rand env cont))
            (set! cont (rator-cont rand env cont))
            (set! exp rator)
            (value-of/k))
  (rator-cont (rand saved-env saved-cont)
              ;; --(value-of/k rand env (rand-cont val cont))
              (set! cont (rand-cont val saved-cont))
              (set! exp rand)
              (set! env saved-env)
              (value-of/k))
  (rand-cont (rator-val saved-cont)
             (let ((rator-proc (expval->proc rator-val)))
               ;; --(apply-procedure rator-proc rator-val cont)
               (set! cont saved-cont)
               (set! proc1 rator-proc)
               (set! val val)
               (apply-procedure/k)))
(define apply-procedure/k
  (lambda ()                          
    (cases proc proc1
      (procedure (var body saved-env)
                 (set! exp body)
                 (set! env (extend-env var val saved-env))
                 (value-of/k)))))

如果val是letrec-exp

ex的情况是:
这个letrec的写法是:
letrec double(x) =if zero?(x) then 0 else -((double -(x,1)),-2) in (double 6)
其中(double 6)是body,double是name,x是var ,if到in中间那一段是body。

  (letrec-exp (p-name b-var p-body letrec-body)
              (value-of letrec-body
                        (extend-env-rec p-name b-var p-body env)))

其中这个extend-env-rec是这样处理的:

(define apply-env
  (lambda (env search-sym)
    (cases environment env
      (empty-env ()
                 (eopl:error 'apply-env "No binding for ~s" search-sym))
      (extend-env (var val saved-env)
                  (if (eqv? search-sym var)
                      val
                      (apply-env saved-env search-sym)))
      (extend-env-rec (p-name b-var p-body saved-env)
                      (if (eqv? search-sym p-name)
                          (proc-val (procedure b-var p-body env))          
                          (apply-env saved-env search-sym))))))

a:

  (letrec-exp (p-names b-vars p-bodies letrec-body)
              (value-of letrec-body
                        (extend-env-rec* p-names b-vars p-bodies env)))
  
  (begin-exp (exp1 exps)
             (letrec 
                 ((value-of-begins
                   (lambda (e1 es)
                     (let ((v1 (value-of e1 env)))
                       (if (null? es)
                           v1
                           (value-of-begins (car es) (cdr es)))))))
               (value-of-begins exp1 exps)))
  
  (assign-exp (var exp1)
              (begin
                (setref!
                 (apply-env env var)
                 (value-of exp1 env))
                (num-val 27)))
(define apply-env
  (lambda (env search-var)
    (cases environment env
      (empty-env ()
                 (eopl:error 'apply-env "No binding for ~s" search-var))
      (extend-env (bvar bval saved-env)
                  (if (eqv? search-var bvar)
                      bval
                      (apply-env saved-env search-var)))
      (extend-env-rec* (p-names b-vars p-bodies saved-env)
                       (let ((n (location search-var p-names)))
                         ;; n : (maybe int)
                         (if n
                             (newref
                              (proc-val
                               (procedure 
                                (list-ref b-vars n)
                                (list-ref p-bodies n)
                                env)))
                             (apply-env saved-env search-var)))))))

b:没有这个函数,b是简化版本的

c:

  (letrec-exp (p-name b-var p-body letrec-body)
              ;; --(value-of/k letrec-body
              ;; --  (extend-env-rec proc-name bvar proc-body env)
              ;; --  cont)
              (set! exp letrec-body)
              (set! env
                    (extend-env-rec p-name b-var p-body env))
              (value-of/k))
(define apply-env
  (lambda (env search-sym)
    (cases environment env
      (empty-env ()
                 (eopl:error 'apply-env "No binding for ~s" search-sym))
      (extend-env (var val saved-env)
                  (if (eqv? search-sym var)
                      val
                      (apply-env saved-env search-sym)))
      (extend-env-rec (p-name b-var p-body saved-env)
                      (if (eqv? search-sym p-name)
                          (proc-val (procedure b-var p-body env))          
                          (apply-env saved-env search-sym))))))

总结

写到这里,基本对比了各个写法。
c其实是为了支持更好的流程控制,比如为了实现goto或error来产生的一种。因为它的所有内容似乎都可以切断?因为都存在寄存器上,并随时可以开始。没想到这件事竟然如此简单。
本来以为尾递归这件事只有符合了一定要求的内容才能实现,没有想过通过语言处理的方式,仅仅通过符号和顺序之间的结合,就可以做到,实在是太惊人了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值