“JRM’s Syntax-rules Primer for the Merely Eccentric” メモ(その4) is-symbol? について
続きです、 is-symbol? について let の変種…というか let を再実装するということで my-let を書きます。
(define-syntax my-let (syntax-rules () ((my-let first-subform . other-subforms) (macro-if (is-symbol? first-subform) (expand-named-let () first-subform other-subforms) (expand-standard-let () first-subform other-subforms))))) ;; XXX: ここではクォートするだけにとどめます (define-syntax expand-named-let (syntax-rules () ((expand-named-let . e) '(expand-named-let . e)))) (define-syntax expand-standard-let (syntax-rules () ((expand-standard-let . e) '(expand-standard-let . e))))
is-symbol? の定義があって、その後に注意を促す一文があります。
(define-syntax is-symbol? (syntax-rules () ((is-symbol? k (form ...)) (return k #f)) ((is-symbol? k #(form ...)) (return k #f)) ((is-symbol? k atom) (letrec-syntax ((test-yes (syntax-rules () ((test-yes) (return k #t)))) (test-no (syntax-rules () ((test-no) (return k #f)))) (test-rule (syntax-rules () ((test-rule atom) (test-yes)) ((test-rule . whatever) (test-no))))) (test-rule (#f))))))
We need to use LETREC-SYNTAX here because we do not want our continuation forms to be in the template of the test-rule; if the test rule matches, the atom we just tested would be rewritten!
これがピンと来ません。整理するために、 is-symbol? を元々の形にします。お手本はこれ。
(define-syntax symbol?? (syntax-rules () ((symbol?? (x ...) kt kf) kf) ; It's a pair, not a symbol ((symbol?? #(x ...) kt kf) kf) ; It's a vector, not a symbol ((symbol?? maybe-symbol kt kf) (let-syntax ((test (syntax-rules () ((test maybe-symbol t f) t) ((test x t f) f)))) (test abracadabra kt kf)))))
これをもとにして、 test のテンプレート部分へ継続フォームを直接展開するようなコードを考えます。
(define-syntax is-symbol? (syntax-rules () ((is-symbol? k (form ...)) (return k #f)) ((is-symbol? k #(form ...)) (return k #f)) ((is-symbol? k maybe-symbol) (let-syntax ((test (syntax-rules () ((test maybe-symbol) (return k #t)) ((test x) (return k #f))))) (test abracadabra))))) ;; (my-let foo ((name value)) body) ;;=> (expand-named-let () abracadabra (((name value)) body))
脱線だけれども、 is-symbol? 内で、 test-rule マクロの呼び出しを (#f) を使っているのは、あえておかしな展開形になるフォームを渡すことで、不意にそのまま展開されたとしても、そこでエラーになるようにしようとしているのかな、と考えられます。シンボルそのままだと下手をしたら気付かない可能性があります。
さて、ここで test マクロの maybe-symbol は is-symbol? マクロのパターン変数なので、入力内容の部分形式で置き換えられます。 (my-let foo ((name value)) body) を例として考えます。
(let-syntax ((test (syntax-rules () ((test foo) ...) ...))) (test abracadabra))
(test foo) のテンプレート部を考えると、
(letrec-syntax ((test (syntax-rules () ((test foo) (return ((if-decide) (expand-named-let . (() foo (((name value)) body))) (expand-standard-let . (() foo (((name value)) body)))) #t)) ...))) (test abracadabra))
識別子 foo が含まれています。これはパターン変数です。 is-symbol? マクロに渡した継続フォーム内の foo も test マクロ呼び出し時の部分形式で置き換えられてしまいます。
test マクロのテンプレート部分に継続フォームが現れないようにすれば上の問題は回避できます。というわけで下のようになる、と。
(define-syntax is-symbol? (syntax-rules () ((is-symbol? k (form ...)) (return k #f)) ((is-symbol? k #(form ...)) (return k #f)) ((is-symbol? k maybe-symbol) (let-syntax ((test-sk (syntax-rules () ((test-sk) (return k #t)))) (test-fk (syntax-rules () ((test-fk) (return k #f))))) (let-syntax ((test (syntax-rules () ((test maybe-symbol) (test-sk)) ((test . x) (test-fk))))) (test (#f)))))))
最初にピンと来なかったと書いたのは、一見大丈夫そうだな、と思ったということで、もっというと手を動かすまでは注意書きを読んでも、その意味するところがわからなかった、というのが正確です。つまり、マクロに現れる k は任意のフォームで、パターン変数となるものが含まれることがある、というのに気付かなかったということなのだけれども、これあらかじめ気付くことができる、もしくはできるようになるだろうか…