“JRM’s Syntax-rules Primer for the Merely Eccentric” メモ(その6) macro-call から

続きます、 macro-call から。

キーワード ! を目印に、そこはマクロ呼び出しをして展開するマクロ、と考えればよいだろうか。

(define-syntax macro-call
  (syntax-rules (!)
    ;; "タグ付き"マクロ用と考えられます。
    ;; 今の所使われていないと思うのでコメントアウト
    ;; ((macro-call k (! ((function ...) . arguments)))
    ;;  (function ... k . arguments))
    ((macro-call k (! (function . arguments)))
     (macro-call ((macro-apply k function)) arguments))
    ((macro-call k (a . b))
     (macro-call ((macro-descend-right k) b) a))
    ((macro-call k whatever) (return k whatever))))

(define-syntax macro-apply
  (syntax-rules ()
    ((macro-apply k function arguments)
     (function k . arguments))))

(define-syntax macro-descend-right
  (syntax-rules ()
    ((macro-descend-right k evaled b)
     (macro-call ((macro-cons k evaled)) b))))

相互再帰する関数(これはマクロだけれども)、特にグローバルな束縛同士のもの、を見ると途端に迷子になってしまう。この感覚は https://t.laafc.net/2018/04/28/L.i.S.P-chapter3f.html#_functions ここで迷子になっていた感覚に近かったので忘れないようにメモ。

単純な例で様子を見ます。

(macro-call () (a . b))
;;-> (macro-call ((macro-descend-right ()) b) a)
;;-> (return ((macro-descend-right ()) b) a)
;;-> (macro-descend-right () a b)
;;-> (macro-call ((macro-cons () a)) b)
;;-> (return ((macro-cons () a)) b)
;;-> (macro-cons () a b)
;;-> (return () (a . b))
;;-> (a . b)

まず、 macro-call 内で (macro-call ((macro-descend-right k) b) a) に展開される時を考えます。パターン変数 b は入力されたS式の"cdr"部で置き換えられます。 return を経由して"car"部が渡されて、再帰的に (macro-call ((macro-cons k "car"部)) "cdr"部) が呼び出されます。言い替えると、"car"部を評価して、今評価した"car"部をconsする継続を伴って再帰的に"cdr"部を評価する、と考えることができると思います。

どうだろう、少しだけ複雑なものでイメージ通りになっているのかどうか、再度様子を見ます。

(macro-call () (a  b))
;;-> (macro-call ((macro-descend-right ()) (b)) a)
;;-> (return ((macro-descend-right ()) (b)) a)
;;-> (macro-descend-right () a (b))
;;-> (macro-call ((macro-cons () a)) (b))
;;-> (macro-call ((macro-descend-right ((macro-cons () a))) ()) b)
;;-> (return ((macro-descend-right ((macro-cons () a))) ()) b)
;;-> (macro-descend-right ((macro-cons () a)) b ())
;;-> (macro-call ((macro-cons ((macro-cons () a)) b)) ())
;;-> (return ((macro-cons ((macro-cons () a)) b)) ())
;;-> (macro-cons ((macro-cons () a)) b ())
;;-> (return ((macro-cons () a)) (b))
;;-> (macro-cons () a (b))
;;-> (return () (a b))
;;-> (a b)

さて、 (macro-call ((macro-apply k function)) arguments) へ展開される方の節を考えます。文字通り、 (! (function ...))function をそのまま入力部で置き換えて作った継続を伴って、 (macro-call 継続 arguments) として macro-call を再帰的に呼び出します。そしてこの継続が、 arguments 部分が再帰的に呼び出されて最終的に return を経由してconsされた結果を伴って呼び出されます。結果、 (function k . arguments) として呼び出されます。つまりこの形式のマクロを埋め込むことができる、ということになります。

(let-syntax
    ((nth2
      (syntax-rules ()
        ((_ k _e0 _e1 e2 _e ...) (return k e2)))))
  (macro-call () (A B (! (nth2 0 1 C 3 4)) D (! (return E)))))
;;-> (A B C D E)

継続を作りながら文中で言うところの “last-in, first-out” していくさまを見ると、この入門の最後に示されるのが評価器そのものというのも頷けてきます。