Gaucheでうまく動かない・・・?

Schemeインタープリタを作る課題が研究室で(学部生向けに)出た。
毎年恒例の課題である。
継続渡しで今日作り終えた。
環境はPLT Scheme、学部時代からお世話になっている環境である。


家のパソコンにPLT Schemeのインストールを忘れていたことに気づき、ふとGaucheを使ってみるか、と考えた。
Windows用のはテスト用だとかどうとか・・・まあ、別にシステム関数が使いたいわけではない。R5RSの環境があればそれでいい。
・・・のだが・・・
どうもおかしい。インタープリタ用環境が更新されない・・・?


調べてみると、どうもidentifier?という述語(独自定義)を使った環境更新関数がちゃんと動かない。identifier?は「引数が識別子として使えるか?(構文のキーワードになっていないか、さらには基本関数と同じ名前になっていないかまで調べる。そういう意味ではR5RSの動作に則ってないインタープリタだが、まあよしとする)」を返す。当然aのようなシンボルはOKである。
はて・・・?といじる。


全く同じ関数を再度定義すると動く。まさか、と思いgaucheを再起動する。
identifier?の表示はこんな感じである。(実際はシャープとタグ状の表示なのだが、HTMLでの表示が面倒なので簡略化)
subr identifier?
ちなみに、独自定義した後はこんな感じである。
closure identifier?
そう、identifier?というなんらかのsubroutineがあるらしいのだ。


さて、面倒なのはここにある。
通常、Schemeという言語は「関数の内部は関数に引数を適用したときの環境で評価する」ということになっている。
例えば、次のようなmy-odd?という関数はmy-even?が定義されていない環境でも定義でき、ごく一部では使用できる。
(define (my-odd? n) (if (= n 0) #f (my-even? (- n 1))))
(my-odd? 0)は#fと評価される。(my-odd? 1)などは全部「my-even?ってなに?」と実行環境に問われて終わる。
この後に、my-even?関数を定義しさえすればよい。
(define (my-even? n) (if (= n 0) #t (my-odd? (- n 1))))
ここまで定義すれば、自然数の範囲で偶奇の判定が可能になる。
このあとで、my-odd?をへんてこな定義に変えてしまったらどうなるか?
(define (my-odd? n) #t)
なんとなんでも正しいと返す関数になってしまった。
このとき、my-even?もまた全て正しいと返す(0の場合はやや異なる事情で正しいと返すのだが、まあその当たりは自分で考えてほしい)。
これは、my-even?に「引数を適用したときの環境」でのmy-odd?が後者で定義したものになっているためである。


さて、元のインタープリタのコードに戻ろう。
実は、環境を更新する関数だが、identifier?という関数を書く「前」に書いてある。
通常ならば、上のように何の問題もないはずである。そう、「通常ならば」。
しかし実際にコードは動かない。一体なぜ?


どうやら、subr(Gaucheの組み込み手続きらしい)と書かれているこのidentifier?だが、どうも先に変数束縛しているような感じだ。つまり、環境更新用の関数の中のidentifier?は、subrのidentifier?であって、closureの(つまり自分が定義した)identifier?ではない、ということである。そうでないと説明が付かない。
というわけで検証をば・・・


今回検証したい性質は、「subrはclosureの束縛とは異なるのではないか?」である。
先ほどのmy-even?などの考え方が役に立ちそうだ。
というわけで、even?やodd?が元のsubrにあったので、これを利用(identifier?が特殊な可能性もあるが、それを「同じオブジェクトとして表示」というのはやや奇妙だと思う)。
(define (my-even1? n) (if (= n 0) #t (odd? (- n 1))))
(my-even1? 2) > #t
(my-even1? 3) > #f
(define (odd? n) (if (= n 0) #f (my-even2? (- n 1))))
(odd? 0) > #f
(odd? 3) > my-even2?ってなに?
まあ、ここまでは意図通り。奇妙なコードなのは気にしない。
(my-even1? 3) > #f
・・・さて、この時点で奇妙なことになっている。明らかに「変」だ。
(define (my-even2? n) (if (= n 0) #t (odd? (- n 1))))
(odd? 3) > #t
(my-even2? 3) > #f
(define (odd? n) #t)
(my-even2? 3) > #t
(my-even2? 4) > #t
(my-even1? 3) > #f


もういいや、飽きたし・・・
後半は、不足してた変数の作成によるodd?の反応と変更による変化を見てみた。
まあここまでで十分だろう。
Gaucheは組み込み関数を「束縛」する。それが評価対象であろうと関数の中であろうと。call-by-valueの考えからすれば変な話なのだが・・・


追記:こんなコードを書くとやはり変。
(define (test) even?)
(define (even n) #f)
(define (test2) even?)
(test) > subr even?
(test2) > closure even?
((test2) 3) > #f
(define (even n) #t)
((test2) 3) > #t
((test) 3) > #f
やっぱり上の解釈でよさそう。