新千葉 ガーベージ・コレクション

FPGA マガジンやインターフェースで書けなかったこと等をちょぼちょぼ書いてます。@ryos36

quoted? と assignment? とdefinition?

quoted? は (quote (a b c)) のように quote で始まるかどうかをチェックしている。lisp ではもっと簡単に '(a b c) と書く。こういう lisp の事情が理解できると quoted? はことさら説明するまでもない。
assignment? は set! で始まる scheme の代入(?)。すでに define されたものしか set! できない。scheme の使い方としては

(set! v 23)

いまある環境に set! したい。環境は入れ子になっている。まず一番下の環境に v があるかを確認する。なければ上の環境を検索し、最終的には大本の環境に行き着く。そこまでやって環境の中に v がなければ scheme としてはエラーだ。common lisp だと setf でなければ自動的に変数が作られるが、scheme ではそうしていない。
各環境は例によって変数のシンボルと値がペアーになっているのでそれを scan することになる。SICP での関数は eval-assignment となっているが、その実体のほとんどは set-variable-value1! だ。common lisp では defun の中に defun を書けないので labels を使った。

(defun set-variable-value! (var val env)
  (labels ((env-loop (env)
                     (if (eq env the-empty-environment)
                       (scheme-error "Unbound variable" var)
                       (let ((frame (first-frame env)))
                         (scan (frame-variables frame) (frame-values frame) env
))))
           (scan (vars vals env)
                 (cond ((null vars)
                        (env-loop (enclosing-environment env)))
                       ((eq var (car vars))
                        (setf (car vals) val))
                       (t (scan (cdr vars) (cdr vals) env)))))
    (env-loop env)))

かなりまどろっこしい。というか環境の構造を知らないとこの関数を読み解くことが出来ない。 もっと簡単に hashtable を使った方が common lisp としてもプログラムとしてもすっきりするだろう(NIY)。

definition? は define ではじまる scheme の定義をするための関数。変数と関数を登録できる。scheme の使い方として変数は

(define v '(a b c))

とすればよい。関数はちょっと複雑だ。一番フォーマルなのは

(define f (lambda (a0 a1) (+ a0 a1)))

のように lambda と明示的にするもの。さて、この define を scheme-eval で評価させると、lambda で始まる式は一度評価される。正確には今、定義しようとしている value 部が一度評価される。つまり '(a b c) なら (a b c) となり quote がとれる。lambda ではじまるなら lambda 式を評価しようとする。したがって、scheme-eval には lambda を評価するための lambda? によるチェックがある。
lambda で始まった式は評価されると単純に make-procedure で procedure に変換される。結果として、f という変数は lambda 式を変換した procedure として記憶される。
例えば、scheme-eval で f を使うとき次のような評価の順をたどる。

(f 3 5) ; f が評価されて procedure に置き換わる
((procedure ....) 3 5) ; procedure が評価される。その実体は (+ a0 a1)
8
(defun define-variable! (var val env)
  (let ((frame (first-frame env)))
    (labels ((scan (vars vals)
                   ;(debug-trace (format t "~a ~a ~%" vars vals))
                   (cond ((null vars)
                          (add-binding-to-frame! var val frame))
                         ((eq var (car vars))
                          (setf (car vals) val))
                         (t (scan (cdr vars) (cdr vals))))))
      (scan (frame-variables frame) (frame-values frame)))))

C でどうする?

そのまま C で書き下せばよい(苦笑)。とくに quote はそのまま書けばよい。unquote とか将来的に拡張されていってもすごく素直に書ける。ここで変に工夫をしてはいけない。
define した変数は最終的に GC で開放されるかもしれない。なので、環境を作るときにちゃんと lisp 風の構造をそのまま持ち込んだ方よい。変に、効率化しようとして(いや最終的には効率化したほうがいいのだが、、、)連想配列を C や C++ で作りこんでしまうと後でえらい目にあう。lisplisp のやりかたがある。

C# でどうする?

C# ではそもそも GC を自前でやることを放棄して、class にしてしまったので、もうなんでもあり。環境も C#連想配列を使えばよい。C# でつくるのは簡単だ、、、