Island Life

< Waldstein | 入れ子のバッククオート >

2015/01/17

簡単で直交性の高い道具を組み合わせる

Explicit-renaming macroは何をやっているかが透明でわかりやすいのだけれど、 それだけでマクロを書くのは面倒だ。

Gaucheの低レベルマクロ機構で出したswap!マクロをもう一度取り上げてみよう。

まず、高レベルマクロのsyntax-rules版。 パターン置換だけで話が済むなら大抵はこれが一番簡潔。

(define-syntax swap!
  (syntax-rules ()
    [(_ a b)
     (let ((tmp a))
       (set! a b)
       (set! b tmp))]))

swap!マクロならこれでいいんだけど、マクロ展開時にパターン置換だけではできない 計算をやろうとすると高レベルマクロでは限界がある。

syntax-case は高レベルマクロの「パターンマッチによる入力の分解機能(1)」および 「マクロ出力の組み立てで適切なリネーミングを行う機能(2)」をそのままに、 (1)と(2)の間に任意のScheme式による計算を入れることを可能にしたものだ。

(define-syntax swap! 
  (lambda (stx) 
    (syntax-case stx () 
      [(_ a b)
       (syntax 
        (let ((value a)) 
          (set! a b) 
          (set! b value)))])))

この例では入力の分解と出力の組み立ての間にやる計算がないので あまり有難味はないけれど、必要なら取り出したaやbを使って複雑なことができる。

一方、ER macroは、hygienic macroに必要な最低限の機能、 つまり「環境を考慮した識別子の比較とリネーム」しか提供しない。 入力の分解と出力の組み立ては自分でやる必要があり、これはかなり面倒くさい。

(define-syntax swap!
  (er-macro-transformer
   (^[f r c]
     (let ([a (cadr f)]
           [b (caddr f)])
       `(,(r'let) ([,(r'value) ,a])
          (,(r'set!) ,a ,b)
          (,(r'set!) ,b ,(r'value)))))))

けれども、入力の分解や出力の組み立てというのは、単なるS式の操作なのだから、 マクロシステムでわざわざ用意しなくても、一般のS式操作ライブラリを使えば良いともいえる。

パターンマッチによるS式の分解についてはGaucheはmatchを持っている。

出力の組み立てについては、「指定の式の識別子をリネームしつつ、必要な箇所に 計算結果を埋め込む」ようなマクロがあればよい。

(define-syntax quasirename-sub
  (syntax-rules (unquote unquote-splicing)
    [(_ rename ()) ()]
    [(_ rename (unquote x)) x]
    [(_ rename ((unquote-splicing x) unquote y)) (append x y)]
    [(_ rename ((unquote-splicing x) . y)) (append x (quasirename-sub rename y))]
    [(_ rename (x unquote y)) (cons (quasirename-sub rename x) y)]
    [(_ rename (x . y)) (cons (quasirename-sub rename x)
                              (quasirename-sub rename y))]
    [(_ rename x) (rename 'x)]))

(define-syntax quasirename
  (syntax-rules ()
    [(_ rename form)
     (let ([xrename (lambda (x)
                      (if (or (symbol? x) (identifier? x))
                        (rename x)
                        x))])
       (quasirename-sub xrename form))]))

これを使うと、ER macro版は次のとおり。

(define-syntax swap!
  (er-macro-transformer
    (^[f r c]
     (match-let1 (_ a b) f
       (quasirename r
        (let ((tmp ,a))
          (set! ,a ,b)
          (set! ,b tmp)))))))

これで手間としてはsyntax-case版と変わらない。しかも、

  • 入力の分解部分は普通のS式のパターンマッチャであり、 マクロ特有の事情を考慮する必要はない。 より強力なパターンマッチャを書けばそれを使うこともできる。
  • 出力の構築部分はquasiquoteと同じセマンティクスである。 パターン変数が特別扱いされていないことに注目。aやbは単なるローカル変数束縛 なのだから、unquoteでその値が取り出せる、それだけである。 もちろんquasirename単独で使うこともできる。例えば識別子にプレフィクスを つけつつ、計算結果を埋め込みたい、とか。

syntax-casesyntax, quasisyntaxも裏では 似たようなことをやってるんだけど、マクロ特有のコンテキストを構文オブジェクトに 隠して持ち運んでいるので、応用が効かない。

(注: ここのquasirenameの定義だと、出力にunquoteやunquote-splicing を含めるのが難しいので、unrenameとか別の名前を使った方が良いかもしれない。)

Tags: Programming, Gauche, Scheme, Macro

Post a comment

Name: