kb84tkhrのブログ

何を書こうか考え中です あ、あと組織とは関係ないってやつです 個人的なやつ

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"

マクロの動作原理はこれでおしまい

 

達成!