4.2.2 具有推迟解释的解释器

4.2.2 具有推迟解释的解释器
在这部分中,我们实现一个正常序的语言,这和scheme相同,除了复合程序在
任何一个参数方面是不严格的。原生的程序是严格的。4.1.1部分的解释器是
不难被修改的。语言它解释这种方式。几乎所有的修改的中心都围绕着程序应用。

基本的思想是,当应用一个程序时,解释器必须确定哪个参数能被评估,哪个能被推迟。
推迟的参数是不被评估的。相反,它们能被转成为对象,叫做待估计的块。待估计的块
必须包括当它被需要时,为了生成实际参数的值而需要的信息。因此,待估计的块必须包括
实际参数表达式和在程序应用中被解释时的环境。

在一个待估计的块中解释表达式的过程叫做强制。总之,一个待估计的块被强制
仅当它的值被需要;当它被传递给一个原生的程序时,这个程序将使用待估计的块
的值;当它是一个条件的判断式的值时;并且当它是一个操作符的值时,这个操作符
将被应用作为一个程序。我们已有一个设计决策是待估计的块是否被记住的事,正如
我们在3.5.1部分中处理的迟延对象一样。具有记忆,第一次待估计的块被强制,
它存储被计算得到的值。接下来的强制,直接返回存储的值,而没有重复的计算。
我们能让我们的解释器有记忆,因为这是对许多的应用更有效率的,然而,这里有许多的注意事项。

* 修改解释器
在推迟的解释器与在4.1部分中的解释器的主要区别是在eval和apply的程序应用
处理方面。

eval的application?子句变成了:

((application? exp)
    (apply (actual-value  (operator exp)  env)  
               (operands exp)
               env
    )
)

这几乎与在4.1部分中的eval的application?子句相同。 对于推迟解释
然而, 我们调用apply有操作数表达式,而不是通过解释它们生成的
实际参数。因为如果实际参数被推迟解释,我们将需要环境来组装
待估计的块, 我们必须通过它。我们仍然解释操作符,因为apply需要
被应用的实际程序,来根据它的类型进行分发并且应用它。

当我们需要一个表达式的实际值时,我们使用:

(define (actual-value  exp env)
    (force-it  (eval exp env))
)

代替仅仅是eval,为了如果表达式的值是一个待解释的块,它将被强制。

我们的apply的新版本也几乎与在4.1.1部分中的版本一样。不同的是
eval已经传递了未解释的操作数的表达式:对于原生的程序(它是严格的)
我们在应用原生的程序之前解释所有的实际参数;对于复合性的程序,
(它是不严格的)在对程序进行应用之前,我们推迟所有的实际参数。

(define (apply procedure arguments)
    (cond  ((primitive-procedure? procedure)
                      (apply-primitive-procedure
                          procedure
                         (list-of-arg-values arguments env)))     ; changed
    
           ((compound-procedure? procedure)
              (eval-sequence (procedure-body procedure)
                                      (extend-environment
                                            (procedure-parameters procedure)
                                            (list-of-arg-values arguments env)   ; changed
                                          (procedure-environment procedure)
                                     )
            ))

           (else (error "Unknown procedure type --APPLY" procedurce))
    )
)

* 表示待估计的块
当程序被应用到参数上并且稍后强制这些待估计的块时,我们的解释必须安排创建
这些待估计的块。一个待估计的块必须把一个表达式和它的环境打包在一起,为了
在稍后的时候能够生成参数的值。为了强制待估计的块,我们能简单地从一个待估计
的块中取出表达式和它的环境,然后在环境中解释表达式。我们使用actual-value
而不是eval,为了表达式的值本身就是一个待估计的块的这种情况。我们将强制,直到
我们到达了某事而不是一个待估计的块:

(define  (force-it obj)
(if (thunk? obj)
    (actual-value  (thunk-exp obj)  (thunk-env  obj))
 obj))

把一个表达式与一个环境打包在一起的一个简单的方式是生成一个列表,
它包括了表达式和环境。因此我们能以如下的方式来创建一个待估计的块:

(define  (delay-it  exp env)
 (list 'thunk  exp env))

(define (thunk? obj)
   (tagged-list?   obj   'thunk)
)

(define (thunk-exp thunk)  (cadr  thunk))
(define (thunk-exp thunk)  (caddr thunk))

实际上,我们所要的不仅仅是这些,还有待估计的块有记忆力。
当一个待估计的块被强制之后,我们把它返回到一个已经解释的块,
并且把存储的表达式替换成它的值,并且修改待估计的块的标签,说明
它能被识别并且已经解释过了。

(define (evaluated-thunk? obj)
      (tagged-list?  obj   'evaluated-thunk)
)

(define (thunk-value  evaluated-thunk)  (cadr  evaluated-thunk))

(define  (force-it obj)
  (cond  ((thunk? obj)
                (let  ((result   (actual-value  (thunk-exp obj)  (thunk-env  obj))))
                        (set-car!  obj  'evaluated-thunk)
                        (set-car!  (cdr obj)  result)   ;  replace exp with its value
                         (set-cdr!  (cdr obj)  '())       ; for unneeded env
                         result
                )
               )
            ((evaluated-thunk? obj)  (thunk-value obj)) 
            (else obj)
  ))

注意的是相同的delay-it能工作在有记忆与没有记忆的情况下。

练习4.27
对于可推迟的解释器,假定我们输入了如下的定义:

(define count  0)
(define (id x)
   (set!   count  (+ count  1))
)

在下面的交互中,填写缺失的内容,并解释你的答案。

(define  w (id (id 10)))
;;;   L-Eval  input
count
;;;;;  L-Eval value
<response>
;;;;   L-Eval  input
w
;;;;;  L-Eval value
<response>
;;;;  L-Eval  input
count
;;;;;   L-Eval value
<response>

练习4.28
eval 使用actual-value而不是  eval来解释操作符,在它把参数传递给 apply,
为了能强制操作符的值。给出一个例子,显示这么做的必要性。

练习4.29
显示一个例子程序,你能期待它,在没有记忆时比有记忆时慢很多。也考虑如下的
交互过程,程序被定义在练习4.27中,count开始于0。

(define (sqaure x) (* x x))
;;;;   L-Eval  input
(square (id 10))
;;;;;  L-Eval value
<response>
;;;;   L-Eval  input
count
;;;;;  L-Eval value
<response>

给出当解释器有记忆和没有记忆时,答案。

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/82948908