問題4-11〜13
「4.1.3 評価器のデータ構造」では評価器の内部的なロジック、特に環境を操作するための定義の詳細について述べられています。「3.2 評価の環境モデル」を読み返しながら、本文で描かれているコードを解読していきました。
問題4-11。フレームの表現方法を変更します。本文では、束縛名のリストと束縛値のリストの対で表現されていましたが、この問題では、各束縛の対を作って、その対のリストがフレームであるようなデータ構造で表現します。フレームの中身を操作するインターフェイスは変更せず、その内部ロジックだけを変更することにします。
(define (make-frame variables values) (if (null? variables) '() (cons (cons (car variables) (car values)) (make-frame (cdr variables) (cdr values))))) (define (frame-variables frame) (map car frame)) (define (frame-values frame) (map cdr frame)) (define (add-binding-to-frame! var val frame) (cons frame (cons var val)))
束縛名のリストを取得する手続きframe-variablesは、それぞれの束縛の対からcarで束縛名を取得し、そのリストを作成します。束縛値のリストを取得する手続きframe-valuesについても同様です。frameに新しい束縛を追加する場合は、ただ単に、frameに束縛名と束縛値の対をconsしてやればよさそうです。
問題4-12。set-variable-value!/define-variable!/lookup-variable-valueの中に存在している似たようなロジックとして、渡された環境の中から指定された束縛を探していくscanという手続きがあります。また、scanの呼び出し方も、first-frameの束縛データを渡しているという点で似ています。これらの点を抽象的に表現する手続きをbinds-controllerとして、その手続きに、操作対象となっている環境(env)、検索対象となる束縛名(var)、渡された環境そのものが存在しなかった場合のアクション(null-env-proc)、環境の中に渡された束縛名がなかった場合のアクション(unmatched-proc)、渡された束縛名を発見した場合のアクション(found-proc)を、引数として渡すことにします。
(define (binds-controller env var null-env-proc unmatched-proc found-proc) (define (scan vars vals) (cond ((null? vars) (unmatched-proc env)) ((eq? var (car vars)) (found-proc vals)) (else (scan (cdr vars) (cdr vals))))) (if (eq? env the-empty-environment) (null-env-proc) (let ((frame (first-frame env))) (scan (frame-variables frame) (frame-values frame))))) (define (lookup-variable-value var env) (binds-controller env var (lambda () (error "Unbound variable" var)) (lambda () (lookup-variable-value var (enclosing-environment env))) (lambda (vals) (car vals)))) (define (set-variable-value! var val env) (binds-controller env var (lambda () (error "Unbound variable -- SET!" var)) (lambda () (set-variable-value! var val (enclosing-environment env))) (lambda (vals) (set-car! vals val)))) (define (define-variable! var val env) (binds-controller env var (lambda () ()) (lambda () (add-binding-to-frame! var val (first-frame env))) (lambda (vals) (set-car! vals val))))
これで動くかどうかは、後ほど検証してみたいと思っています。
問題4-13。一度定義した束縛を除去するためのunbind!を作成します。環境を渡り歩きながら、最初に発見した場所で、その束縛を除去するという仕様にします。
(define (unbind! var env) (binds-controller env var (lambda () (error "Unbound variable -- UNBIND!" var)) (lambda () (unbind! var (enclosing-environment env))) (lambda (vals) (set-car! env (replace-var! (first-frame env) var)))))
replace-var!は、渡されたフレームを、同じく渡されたvarという束縛を除外したフレームに置き換えます。ここは、frameのデータ構造によって、操作方法が変わってきますが、本文に掲載されていたデータ構造の場合は、以下のようになるかと思います。
(define (replace-var! frame var) (define (remake-frame new-frame vars vals) (cond ((null? vars) '()) ((eq? var (car vars)) (remake-frame new-frame (cdr vars) (cdr vals))) (else (remake-frame (add-binding-to-frame! (car vars) (car vals) new-frame) (cdr vars) (cdr vals))))) (remake-frame (make-frame '() '()) (frame-variables frame) (frame-values frame)))
かなり汚いコードになってしまいました。正しく動くかどうかは未検証ですので、これも今後の課題としていきたいと思います。