1. 程式人生 > >5.4.2 序列化解釋與尾遞迴

5.4.2 序列化解釋與尾遞迴

5.4.2 序列化解釋與尾遞迴
顯式控制的直譯器在ev-sequence的部分與元迴圈的直譯器的eval-sequence
程式是很相似的。它處理在程式體中的表示式的序列或者是顯式的begin表示式。

通過把要解釋的表示式的序列放入unev,在棧上儲存continue,跳轉到ev-sequence,
就解釋了顯式的begin表示式。

ev-begin
  (assign unev (op begin-actions) (reg exp))
  (save continue)
  (goto (label ev-sequence))

通過從compound-apply跳轉到ev-sequence,已經被放在棧上的continue暫存器,被儲存
在ev-application中,在程式體中的隱式的序列就被處理完成了。

在ev-sequence和ev-sequence-continue中的入口形成了迴圈,連續地解釋序列中的每一個
表示式。未被解釋的表示式的列表被儲存在unev。 在解釋每個表示式之前,我們檢檢視看是否
在序列中有其它的要被解釋的表示式。如果有,我們儲存未解釋的表示式的其它部分(在unev中)和它們的環境(在env中)和呼叫eval-dispatch來解釋表示式。在ev-sequence-continue中,從這個解釋返回時,這兩個儲存的暫存器被恢復。

在序列中的最後的一個表示式被處理得有點不同,在入口點ev-sequence-last-exp.
因為在這個以後,沒有更多的表示式被解釋了,在去eval-dispatch之前,我們
不需要儲存unev和env. 整個序列的值是最後一個表示式的值,所以最後一個表示式被
解釋之後沒有什麼留下來要做,除了繼續去在棧上儲存的入口點(它在ev-application或者是ev-begin中被儲存的)。不是設定continue為eval-dispatch能夠返回這裡,也不是從棧上恢復,再繼續 這個入口點,我們在去eval-dispatch之前,從棧上恢復continue,為了eval-dispatch在解釋
了表示式後,能繼續。

ev-sequence
  (assign exp (op first-exp) (reg unev))
  (test (op last-exp?) (reg unev))
  (branch (label ev-sequence-last-exp))
  (save unev)
  (save env)
  (assign continue (label ev-sequence-continue))
  (goto (label eval-dispatch))
ev-sequence-continue
  (restore env)
  (restore unev)
  (assign unev (op rest-exps) (reg unev))
  (goto (label ev-sequence))
ev-sequence-last-exp
  (restore continue)
  (goto (label eval-dispatch))

*    尾遞迴
在第一章中,我們說過如下的程式描述的過程:

(define (sqrt-iter guess x)
  (if (good-enough? guess x)
      guess
      (sqrt-iter (improve guess x)
                 x)))

是一個迭代的過程。即使,程式是語法性的遞迴(用它自身定義的)對於直譯器,
邏輯上不需要儲存資訊,從sqrt-iter的一個呼叫到另一個。一個直譯器可能執行
一個程式例如sqrt-iter而沒有增加儲存,而程式在呼叫自身,被叫做一個尾遞迴的
直譯器。在第四章中的直譯器的元迴圈的實現沒有指定直譯器是否是尾遞迴的,因為
直譯器繼承了從底層的scheme的儲存狀態的機制。在顯式控制的直譯器中,
然而,我們能跟蹤解釋的過程,來看看,當程式呼叫引起的在棧上的資訊的淨累加
情況。

我們的直譯器是尾遞迴的,因為為了解釋一個序列的最後一個表示式,我們直接傳遞
給eval-dispatch而沒有在棧上儲存任何的資訊。因此,解釋在序列中的最後一個表示式,
即使它是一個程式呼叫(如sqrt-iter,當條件表示式,它是程式體中的最後一個表示式,歸結
為一個對sqrt-iter的呼叫) 將沒有引起棧上的任何資訊的累加。

如果我們不利用這個事實(在這個例子中儲存資訊是不必要的),我們可能
實現eval-sequence,通過對待一個序列中的所有的表示式以一種相同的方式,
儲存暫存器,解釋表示式,返回到恢復的暫存器,重複這個過程直到所有的
表示式都被解釋了。

ev-sequence
  (test (op no-more-exps?) (reg unev))
  (branch (label ev-sequence-end))
  (assign exp (op first-exp) (reg unev))
  (save unev)
  (save env)
  (assign continue (label ev-sequence-continue))
  (goto (label eval-dispatch))
ev-sequence-continue
  (restore env)
  (restore unev)
  (assign unev (op rest-exps) (reg unev))
  (goto (label ev-sequence))
ev-sequence-end
  (restore continue)
  (goto (reg continue))

這可能似乎是對一個序列的解釋的之前版本的程式碼,有一點修改。僅有的不同是
在一個序列的最後一個表示式時像其它的表示式一樣,進行了儲存與恢復的過程。
直譯器將仍然能夠為任何的表示式得到相同的值。但是這個修改對於尾遞迴的實現
卻是致命的,因為我們現在在解釋了序列中的最後一個表示式後,必須返回,為了恢復
無用的暫存器的儲存值。在一個程式呼叫的巢狀之中,這些額外的儲存將累加。
所以,過程例如sqrt-iter將要求空間是與迭代的次數成比例的,而不是一個常數的
空間大小。這種不同能夠成為重要的。例如,在尾遞迴中,一個無限的迴圈能被表達為
僅使用程式呼叫的機制:

(define (count n)
  (newline)
  (display n)
  (count (+ n 1)))

沒有了尾遞迴,這樣的程式最終將耗盡棧上的空間,表達一個真正的迭代將需要一些控制
機制而不是程式呼叫。