先日、「Gauche の Explicit-renaming でスレッドマクロ」という表題で Scheme 関連の記事が投稿されていたのを読みました。 この元記事は Clojure にあるスレッドマクロを再現しようとする試みですが、引数を適用する箇所を自由にする拡張の部分が興味深いです。
このマクロはアナフォリックマクロになっています。 アナフォリックマクロというのは特定の名前の束縛を暗黙に行うようなマクロのことです。
syntax-rules
でアナフォリックマクロ
アナフォリックマクロは syntax-rules
で出来ない種類のマクロであると説明されることがしばしばあります。 しかし、少し回りくどい技巧を用いれば不可能ではありません。
実際に syntax-rules
でやってみます。 ここでは R7RS の仕様に沿ってライブラリとして書きましたが、 define-library
構文を除けば R5RS にも R6RS にも合致するはずです。
(define-library (threading-macro)
(export % -> ->>)
(import (scheme base))
(begin
(define-syntax % (syntax-rules ()))
(define-syntax let1
(syntax-rules ()
((_ var expr b0 b1 ...)
(let ((var expr)) b0 b1 ...))))
(define-syntax if-let1
(syntax-rules ()
((_ var expr then)
(let1 var expr (if var then)))
((_ var expr then alt)
(let1 var expr (if var then alt)))))
(define-syntax ->
(syntax-rules ()
((_ cur) cur)
((_ cur (proc args ...) rest ...)
(if-find-% (args ...) (args ...)
(cur (if-let1 next (proc args ...) (-> next rest ...) #f))
(if-let1 next (proc cur args ...) (-> next rest ...) #f)))
((_ cur proc rest ...)
(if-let1 next (proc cur) (-> next rest ...) #f))))
(define-syntax ->>
(syntax-rules ()
((_ cur) cur)
((_ cur (proc args ...) rest ...)
(if-find-% (args ...) (args ...)
(cur (if-let1 next (proc args ...) (->> next rest ...) #f))
(if-let1 next (proc args ... cur) (->> next rest ...) #f)))
((_ cur proc rest ...)
(if-let1 next (proc cur) (->> next rest ...) #f))))
(define-syntax if-find-%
(syntax-rules (%)
((_ (x . xs) (y . ys) t f)
(if-find-% x y
t
(if-find-% xs ys t f)))
((_ % x t f) (let1 x . t))
((_ x y t f) f)))
))
実行例
このように使うと……
(import (scheme base)
(scheme write)
(threading-macro))
(write (->> 2 (list % 1)))
きちんと (2 1)
という結果が表示されます。
どうなってんの?
特定の名前の束縛を暗黙に作るのがアナフォリックマクロであると最初に説明しました。 逆に言えば、それが出来ないはずの syntax-rules
では束縛される名前は陽に与えなければならないということです。
いや、 %
は与えられているではないか! というのが syntax-rules
でアナフォリックマクロを作る考え方です。
(->> 2
(list % ;; ← ここに % は有るじゃん!
1))
与えられたフォームの中から %
を探してそれを出力に転写することでアナフォリックマクロが実現できるのです。
欠陥
とはいうものの、感覚的に不自然な挙動をする場合もあるので、汎用的なユーティリティとしてはあまりお勧めできるものではありません。
元記事で提示されているマクロと同程度ならば syntax-rules
でも出来そうだと思ったことからやってみたという余興なので、あまり積極的に参考にしないでください。