RacketのMacroを調べてみる (2)
今回はmacroの原理について
まずはtransformerから
といっても実はただの関数です
(define-syntax foo
(lambda (stx)
(syntax "I am foo")))
defineの代わりにdefine-syntaxを使うとマクロの定義になります
ていうかこの文脈ではtransformerの定義と言ったほうがいいのかな
(lambda 〜)の部分がtransformerと呼ばれる関数
とりあえず動かしてみます
> (foo)
"I am foo"
コンパイラはtransformerが返したsyntaxが元からソースに書いてあったかのように
処理を行います
この例ではtransformerは(syntax "I am foo")を返します
するとコンパイラは"I am foo"がソースに書いてあったかのように処理を進め、
結果"I am foo"が出力されます
いつものdefineと同じように、短くも書けます
(define-syntax (also-foo stx)
(syntax "I am also foo"))
さらに、syntaxは#'と省略することができます
(define-syntax (say-hi stx)
#'(displayln "hi"))
これまでの例は入力を無視してました
入力はどうなってるんでしょうか
> (define-syntax (show-me stx)
(print stx)
#'(void))
> (show-me '(+ 1 2))
#<syntax:11:2 (show-me (quote (+ 1 2)))>
stxには#<syntax:11:2 (show-me (quote (+ 1 2)))>なるものが入ってました
これがsyntax objectと呼ばれるものです
ざっくりソースコードの断片のことと思ってればよさそうです
この場合(show-me (quote (+ 1 2))っていうコードを表してます
やっと、何かする感じの例です
reverse-meは引数を逆順に並べ替えてから実行してくれます
> (define-syntax (reverse-me stx)
(datum->syntax stx (reverse (cdr (syntax->datum stx)))))
> (reverse-me "backwards" "am" "i" values)
"i"
"am"
"backwards"
解説しよう
コンパイラがreverse-meを見つけると、それを含む関数全体、つまり
(reverse-me "backwards" "am" "i" values))をsyntax objectとして
stxに入れてtransformerに渡します
syntax->datumはsyntax objectからコードだけを抜き出して普通のS式に
して返してくれます
> (syntax->datum #'(reverse-me "backwards" "am" "i" values))
'(reverse-me "backwards" "am" "i" values)
これをcdrしてreverseすると
> (reverse (cdr '(reverse-me "backwards" "am" "i" values)))
'(values "i" "am" "backwards")
なんか関数っぽくなりましたがまだこれはただのS式で、コンパイラはコードと
思ってくれませんので、datum->syntaxを使ってsyntax objectにしてやります
> (datum->syntax #f '(values "i" "am" "backwards"))
<syntax (values "i" "am" "backwards")>
transformerがこのsyntax objectをコンパイラに返すと、
コンパイラは(values "i" "am" "backwards")という式がソースに
書いてあると思って実行します
(valuesは複数の値を返す関数です)
> (values "i" "am" "backwards")
"i"
"am"
"backwards"
マクロの動作原理はこれでおしまい
達成!