評価プログラムは式を簡約し, ついには基本手続きの作用にまで行く. 従って評価器を走らせるのに必要なものは, 基本手続きの作用をモデル化するために, 基盤になるLispシステムを呼び出す機構を作ることである.
evalが基本手続きの作用の演算を評価する時, applyに渡すべきオブジェクトが見つかるように, 各基本手続き名に対して, 束縛が必要である. そこで, 一義的なオブジェクトを, 評価しようとしている式に現れ得る基本手続きの名前に対応づける 大域的環境を設定する. 大域的環境にはまた評価する式で変数として使えるように, 記号 trueとfalseの束縛もある.
(define (setup-environment)
(let ((initial-env
(extend-environment (primitive-procedure-names)
(primitive-procedure-objects)
the-empty-environment)))
(define-variable! 'true true initial-env)
(define-variable! 'false false initial-env)
initial-env))
(define the-global-environment (setup-environment))
applyが手続きprimitive-procedure?と apply-primitive-procedureを使って基本手続きオブジェクトを識別出来, 作用出来れば, 基本手続きオブジェクトをどう表現するかは, あまり関係しない. 記号primitiveで始り, 基本手続きを実装している基盤Lispでの手続きを含むリストとして, 基本手続きを表現するように選んだ.
(define (primitive-procedure? proc) (tagged-list? proc 'primitive)) (define (primitive-implementation proc) (cadr proc))
setup-environmentは基本手続きの名前と実装手続きをリスト:
(define primitive-procedures
(list (list 'car car)
(list 'cdr cdr)
(list 'cons cons)
(list 'null? null?)
〈基本手続きが続く〉
))
(define (primitive-procedure-names)
(map car
primitive-procedures))
(define (primitive-procedure-objects)
(map (lambda (proc) (list 'primitive (cadr proc)))
primitive-procedures))
からとる:16
基本手続きを作用させるには, 基盤Lispを使い, 実装手続きを引数に作用させるだけである:17
(define (apply-primitive-procedure proc args) (apply-in-underlying-scheme (primitive-implementation proc) args))
超循環評価器を走らせるのを便利にするため, 基盤のLispシステムの読込み- 評価-印字ループをモデル化する駆動ループ(driver loop)を用意する. これは 促進記号(prompt)を印字し, 入力式を読み込み, この式を大域環境で評価し, 結果を印字する. 印字された結果の前に出力記号 (output prompt)をつけ, 式の値を他の印字された出力から区別出来るようにする.18
(define input-prompt ";;; M-Eval input:")
(define output-prompt ";;; M-Eval value:")
(define (driver-loop)
(prompt-for-input input-prompt)
(let ((input (read)))
(let ((output (eval input the-global-environment)))
(announce-output output-prompt)
(user-print output)))
(driver-loop))
(define (prompt-for-input string)
(newline) (newline) (display string) (newline))
(define (announce-output string)
(newline) (display string) (newline))
非常に長くなりそうな(または循環するかも知れない)合成手続きの環境部分を避けるため, 特別な印字手続きuser-printを使う.
(define (user-print object)
(if (compound-procedure? object)
(display (list 'compound-procedure
(procedure-parameters object)
(procedure-body object)
'))
(display object)))
評価器を走らせるのになすべきは, 大域環境を初期化し, 駆動ループを起動することである. 対話の例を示す.
(define the-global-environment (setup-environment))
(driver-loop)
;;; M-Eval input:
(define (append x y)
(if (null? x)
y
(cons (car x)
(append (cdr x) y))))
;;; M-Eval value:
ok
;;; M-Eval input:
(append '(a b c) '(d e f))
;;; M-Eval value:
(a b c d e f)
16
基盤Lispで定義されたどの手続きも, 超循環評価器では基本手続きとして使うことが出来る. 評価器に組み込まれた基本手続きの名前は基盤Lispでの実装の名前と同じである必要はない; ここで名前が同じであるのは超循環評価器はScheme自身を実装しているからである. そこで, 例えば,
(list 'first car)または(list 'square (lambda (x) (* x x)))をprimitive-proceduresのリストに置くことが出来る.
17
apply-in-underlying-schemeは前の章で使ったapply手続きである. 超循環評価器のapply手続き(4.1.1節)は,
この基本手続きの仕事をモデル化している. applyという二つの異るものがあるのは, 超循環評価器のapplyの定義が基本手続きの定義を隠すので,
超循環評価器を走らせる時に技術的な問題になる. 一つのやり方は超循環applyの名前を替え, 基本手続きの名前との衝突を避けることである. ここではそうではなく, 超循環のapplyを定義する前に, 基盤のapplyへの参照を
(define apply-in-underlying-scheme apply)
とすることで退避したと仮定する. こうすると元々の版のapplyに別の名前でアクセス出来る.
18
基本手続き
readは, 利用者からの入力を待ち, 入力された次の完全な式を返す. 例えば利用者が(+ 23 x)と入力すると, readは記号+, 数23
と記号xからなる三要素のリストを返す. 利用者が
'xと入力すると, readは記号quoteと記号xからなる二要素のリストを返す.