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

続きます、 return から。

まず sreverse マクロの中身を見てゆくことになります。例えば after-head の所を考えます。

    ((srev "after-tail" new-tail head kont)
     (srev "top" head ("after-head" new-tail kont)))
    ((srev "after-head" new-head (new-tail ...) (tag . more))
     (srev tag (new-tail ... new-head) . more))

最初の節で、 head を処理するところ、 "after-head" 継続を作ります、これが ("after-head" new-tail kont) の箇所。で head を再帰的に処理して、今作った "after-head" 継続が呼び出されます、二番目のパターン。ここで同時に、後続の継続を (tag . more) で取り出して呼び出します、最後の (〜 . more) の所です。

ここで言われている継続はリストで、 tag を取り出して呼び出し、パターンマッチで継続を作成した時の任意の値を取り出しています。 (マクロ名 タグ 引数 . 継続) として呼び出して、 (マクロ名 タグ 引数 任意の値 継続) としてパターンマッチして受け取る、と。

特に、この"任意の値"が複数になるかもしれないとしたら (sreverse ではないこと)、パターンマッチで後続の継続を取り出すことがわずらわしくなりそうです。一方で、継続が呼び出される際には、"引数"として常に一つの値のみが渡される、ということになると考えられます。

出てくるのが return マクロ。

(define-syntax return
  (syntax-rules ()
    ;; Continuation goes first. Location of return value is indicated
    ;; by end of first list.
    ((return ((kbefore ...) . kafter) value)
     (kbefore ... value . kafter))
    ;; Special case to just return value from the null continuation.
    ((return () value) value)))

;; 動作を確認するためマクロ集
(define-syntax macro-null?
  (syntax-rules ()
    ((macro-null? k ()) (return k #t))
    ((macro-null? k otherwise) (return k #f))))

(define-syntax macro-car
  (syntax-rules ()
    ((macro-car k (car . cdr)) (return k car))
    ((macro-car k otherwise) (syntax-error "Not a list"))))

(define-syntax macro-cdr
  (syntax-rules ()
    ((macro-cdr k (car . cdr)) (return k cdr))
    ((macro-cdr k otherwise) (syntax-error "Not a list"))))

(define-syntax macro-pair?
  (syntax-rules ()
    ((macro-pair? k (a . b)) (return k #t))
    ((macro-pair? k otherwise) (return k #f))))

(define-syntax macro-list?
  (syntax-rules ()
    ((macro-list? k (elements ...)) (return k #t))
    ((macro-list? k otherwise) (return k #f))))

(define-syntax macro-cons
  (syntax-rules ()
    ((macro-cons k ca cd) (return k (ca . cd)))))

(define-syntax macro-append
  (syntax-rules ()
    ((macro-append k (e1 ...) (e2 ...))
     (return k (e1 ... e2 ...)))))

触って様子を見ます。

(macro-cons () a b)
;;-> (a . b)

(macro-cons ((macro-list? ())) a b)
;;-> #f

(macro-cons ((macro-list? ((macro-cons () #t)))) a b)
;;-> (#t . #f)

(let-syntax
    ((my-macro-list
      (syntax-rules ()
        ((_ k e ...) (return k (e ...))))))
  (macro-cdr ((my-macro-list () a b c)) (_ . CDR)))
;;-> (a b c CDR)

まず先程挙げた、任意の値として任意の数の値を取り出すマクロを組み合わせるのが容易になっていることがポイントかなと考えられます。ここで、普通の関数呼び出しに近い見た目になって欲しいかもしれません。ということで示されるのが macro-subproblem です。


(define-syntax macro-subproblem
  (syntax-rules ()
    ;; "タグ付き"マクロ用の節
    ((macro-subproblem before ((macro-function ...) . args) . after)
     (macro-function ... (before . after) . args))
    ((macro-subproblem before (macro-function . args) . after)
     (macro-function (before . after) . args ))))

;; (list 'a 'b 'c (cdr '(_ . CDR))) ;; これを念頭にして…
;;; => (a b c CDR)

(macro-subproblem (my-macro-list () a b c) (macro-cdr (_ . CDR)))
;;-> (macro-cdr ((my-macro-list () a b c)) (_ . CDR))
;;-> (a b c CDR)

(macro-subproblem (macro-list? ()) (macro-cons a b))
;;-> (macro-cons ((macro-list? ())) a b)
;;-> #f

ただこれ直観的なのだろうか、という疑問が残ってしまいます…。上で「触ってみます」、と書いた所で継続を直接書き下ろして色々試行錯誤していたのが影響しているのか、この macro-subproblem を使用してみると、かえって違和感を憶えてしまいます…でも後々になって見返すとそうでもないのかもしれない、とも思います…

そして macro-if です。

(define-syntax macro-if
  (syntax-rules ()
    ((macro-if (pred . args) if-true if-false)
     (pred ((if-decide) if-true if-false) . args))))

(define-syntax if-decide
  (syntax-rules ()
    ((if-decide #f if-true if-false) if-false)
    ((if-decide otherwise if-true if-false) if-true)))

(macro-if (macro-list? ()) onsequent alternative)
;;-> (macro-list? ((if-decide) onsequent alternative) (()))
;;-> (return ((if-decide) onsequent alternative) #t)
;;-> (if-decide #t onsequent alternative)
;;-> consequent

if-decide 継続を作って pred の結果が渡るようにしています。 macro-subproblem と比較すると今度は解り易いような気がします。 if-decide マクロは (if-decide 引数 true節 false節) で呼び出されていて、これは (マクロ名 引数 任意の値 ...) の形です。"任意の値"の方が継続なのが先述のマクロ( macro-null? 等)とは対照的です。