Lisp on Ruby に ’ と let と gensym を実装した
quote はあったがリードマクロの ' がなかったので実装した。
とは言っても、本当のリードマクロのように関数に処理がディスパッチされるわけではなくて、組み込みだ。
前回、rotatef を次のように定義した。
(defmacro rotatef (a b) (.list (quote progn) (.list (quote setq) (quote tmp) a) (.list (quote setq) a b) (.list (quote setq) b (quote tmp)) (quote nil)))
それがこうなった。
(defmacro rotatef (a b) (setq tmp (.gensym)) (.list 'let (.list (.list tmp a)) (.list 'setq a b) (.list 'setq b tmp) 'nil))
ついでに let も作ったので、以前は変数 tmp が呼び出し元の環境に漏れだしていたのが、閉じ込められるようになった。
また、gensym も使うようにしたので、a か b が呼び出し元で tmp という名前でも正しく動作する。
let の定義はこうだ。可読性が低くて、すまない。
(defmacro let (varlist &rest body) (.list 'apply (+ '(.list) (map varlist & #'last)) '& (+ (.list 'lambda (.list (map varlist & #'first))) body)))
apply というのは Object に定義されている自作のメソッドで、ブロックをレシーバーに適用するだけだ。
3.apply { |x| [ x**2, x**3 ] } => [9, 27]
Javascript の apply メソッドとは引数の順番が逆になっているが、これは関数適用一般というよりは let のような用法を想定しているためだ*1。
gensym は環境のシンボルとの衝突は気にしないが、連番のシンボルを生成する。
> (.gensym) :g_00001 > (.gensym) :g_00002 > (.gensym) :g_00003
定義は次のようになっている。
(let ((counter 0)) (.define_method 'gensym &(lambda () (to_sym (% "g_%05x" (setq counter (+ counter 1)))))))
おもしろいのは Ruby のメソッドはふつう、定義時に変数をキャプチャできないのだが、define_method を使うことで counter という変数をキャプチャできる。この counter はさっき作った let のおかげで、ここだけで有効なので環境に漏れ出すことはない。
いずれはバッククオートも作りたい。
*1:つまりレシーバーの式に局所的に名前をつけて計算する。Object#tap も似たようなメソッドだがあれはブロックの評価結果を捨ててしまう。