begin, recur, retry
オリジナル言語の新しい文法を妄想してやまないとき、継続や末尾再帰を使いたいなら Scheme で、それ以外なら Common Lisp でマクロなり何なりを書いて実験してみるのだけれど、実際にそうやって作ってしまうと、じゃあ新言語じゃなくてこれ使えばいいじゃん、ということになって妄想は妄想のまま終わるのでした。
それはそれとして、今回のはシンプルなループ・マクロ。
(defmacro begin (&body body) `(begin-helper () () ,body)) (defmacro begin-helper (syms vals body) (if (and (>= (length body) 3) (eq (second body) '=)) `(begin-helper (,@syms ,(first body)) (,@vals ,(third body)) ,(cdddr body)) `(block begin (labels ((recur ,syms ,@body) (retry ,syms (return-from begin (recur ,@syms)))) (recur ,@vals)))))
begin ブロックの中で recur と retry という2つの局所関数を定義する。前者は普通の再帰で、後者は処理の残りを捨てて次のループに入るというC言語の continue 文みたいなイメージ。
おまけとして、ブロックの先頭で sym = val みたいな形で変数宣言を書けるようにした。recur や retry に与えた引数はこの値を更新する。
まずリストを逆順にする処理を recur で書くとこうなる:
CL-USER> (begin sub = '(1 2 3) acc = '() (if sub (recur (cdr sub) (cons (car sub) acc)) acc)) (3 2 1)
普通の末尾再帰だ。一方、retry を使うと続きの処理がすっ飛ばされるので以下のようにも書ける:
CL-USER> (begin sub = '(1 2 3) acc = '() (when sub (retry (cdr sub) (cons (car sub) acc))) acc) (3 2 1)