EIODのquasiquoteの謎
追記: dをdepthのdとしてコメントを追加。RnRS用法( http://practical-scheme.net/wiliki/wiliki.cgi?R6RS%3A%E7%BF%BB%E8%A8%B3%3AR6RS%3A11.17%20Quasiquotation )。see comment.
EIOD( https://github.com/okuoku/eiod-r6rs/blob/master/eiod.scm )のquasiquote実装が異常にシンプルなのに気付いた。
(define-syntax quasiquote (syntax-rules (unquote unquote-splicing quasiquote) (`,x x) (`(,@x . y) (append x `y)) ((_ `x . d) (cons 'quasiquote (quasiquote (x) d))) ((_ ,x d) (cons 'unquote (quasiquote (x) . d))) ((_ ,@x d) (cons 'unquote-splicing (quasiquote (x) . d))) ((_ (x . y) . d) (cons (quasiquote x . d) (quasiquote y . d))) ((_ #(x ...) . d) (list->vector (quasiquote (x ...) . d))) ((_ x . d) 'x)))
かなりわけがわからない。省略されている、unquote unquote-splicing quasiquoteをシンボルに戻すと:
(define-syntax quasiquote (syntax-rules (unquote unquote-splicing quasiquote) ;; 入力を(quasiquote x d ...)としたとき、dは空リストのリストになり、D = (length d)とする。 ;; Dをカウントアップ/ダウン/ゼロするルールと ;; Dの長さがマッチ条件になっているルールがある。通常の利用時は当然D = 0でD != 0は内部的に使用する。 ((quasiquote (unquote x)) x) ;; ← ★ OUT1 (D == 0でマッチ) ((quasiquote ((unquote-splicing x) . y));; ← ★ OUT2 (D == 0でマッチ) (append x (quasiquote y))) ((quasiquote (quasiquote x) . d) ;; ← ★ SELF1 (D++) (cons 'quasiquote (quasiquote (x) d))) ((quasiquote (unquote x) d) ;; ← ★ SELF2 (D > 0でマッチ, D--) (cons 'unquote (quasiquote (x) . d))) ((quasiquote (unquote-splicing x) d) ;; ← ★ SELF3 (D > 0でマッチ, D--) (cons 'unquote-splicing (quasiquote (x) . d))) ((quasiquote (x . y) . d) ;; ← ★ CONS1 (任意のDにマッチ) (cons (quasiquote x . d) (quasiquote y . d))) ((quasiquote #(x ...) . d) ;; ← ★ VECTOR1 (任意のDにマッチ) (list->vector (quasiquote (x ...) . d))) ((quasiquote x . d) 'x))) ;; ← ★ OUT3 (任意のDにマッチ, D = 0)
OUT1、OUT2、OUT3は理解できる。
OUT3の d は捨ててしまって大丈夫なのかという気持ちになるが、通常のquasiquoteであれば、
(quasiquote x) ↓ ;; 真正リストの最終ペアは空リスト (quasiquote x . '()) ↓ ;; OUT3にマッチ (quote x)
となって正しい挙動であることがわかる。
OUT2はCONS1の変形で、実際にステップを踏むと、
(quasiquote (1 (unquote-splicing lis))) ↓ ;; ★ CONS1 (cons (quasiquote 1) (quasiquote ((unquote-splicing lis))) ↓ ;; 真正リストの最終ペアは空リスト (cons (quasiquote 1) (quasiquote ((unquote-splicing lis) . '())) ↓ ;; car部がOUT3、cdr部がOUT2 (cons 1 (append lis '()))
VECTOR1も、単に一旦vector→listしてからquasiquoteを適用し、list→vectorしているだけと言える
((quasiquote #(x ...) . d) ;; ← ★ VECTOR1改 (list->vector (quasiquote (vector->list #(x ...)) . d)))
もちろん実際にはこのような書き換えをすると動かない。
CONS1も、単に対を分解してcar部cdr部の両者にquasiquoteを適用して、後からconsし直しているだけと言える。
SELF1、SELF2、SELF3がなかなか上手く説明できない。これらはunquote unquote-splicing quasiquoteの3者をquasiquoteでクオートする際の例外ケースに相当する。
正当なquasiquoteの呼び出しは1項、つまり、
(quasiquote HOGE)
でなければならないので、SELF2とSELF3はSELF1で生成されるquasiquoteでしか生成されないことになる。SELF1はquasiquoteが入れ子になったケースを表わすので、OUT1で生成される2項のquasiquoteを使って、quasiquoteが入れ子になっていることを表現しているように思える。
ただ、他のScheme実装ではここまで単純ではないので、入れ子の処理はもうちょっと真面目にやらないと不味いのかもしれない。