RacketのMacroを調べてみる (3)
なぜ関数ではなくてmacroで書くんでしょうか
たとえば、ifを自分で定義してみます
> (define (our-if condition true-expr false-expr)
(cond [condition true-expr]
[else false-expr]))
一見うまく行きそうですがそうは問屋がおろしません
> (define (display-and-return x)
(displayln x)
x)
> (our-if #t
(display-and-return "true")
(display-and-return "false"))
true
false
"true"
返す値は"true"で合ってるんですが、
trueの節だけでなくfalseの節も実行されていることがわかります
これは困ります
これは、Racketでは関数を呼び出す前に引数をすべて評価するルールと
なっていることによります
our-ifの中を実行する前に (display-and-return "true") も
(display-and-return "false") も実行されてしまっています
これは関数の側ではどうしようもありません
このルールを回避するためにマクロが必要となるわけです
マクロには、引数がそのまま渡されるので上のような問題はありません
> (define-syntax (our-if-v2 stx)
(define xs (syntax->list stx))
(datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
[else ,(cadddr xs)])))
> (our-if-v2 #t
(display-and-return "true")
(display-and-return "false"))
true
"true"
うまくいきました
どう動いているか細かく見ていきます
まずstxに元の式が入ります
> (define stx #'(our-if-v2 #t "true" "false"))
> (displayln stx)
<syntax (our-if-v2 #t "true" "false")>
元記事の例をそのまま使ったんですが、これだと引数が評価されずに
渡されてるかどうかわかりませんね
まあいいか
元の式と結果の表示を少し端折ってます
つぎはstxの項をばらしてsyntaxのlistにします
> (define xs (syntax->list stx))
> (displayln xs)
(<syntax our-if-v2> <syntax #t> <syntax "true"> <syntax "false">)
xsからcad...rで項目を取り出し、condの形にします
バッククォートはもし意味がわからなくてもたぶんここでしか出てこないので
問題ありません
> `(cond [,(cadr xs) ,(caddr xs)]
[else ,(cadddr xs)])
'(cond (<syntax #t> <syntax "true">) (else <syntax "false">))
この式全体をsyntaxに変換します
> (datum->syntax stx `(cond [,(cadr xs) ,(caddr xs)]
[else ,(cadddr xs)]))
<syntax (cond (#t "true") (else "fals...>
あとはコンパイラが (cond (#t "true") (else "false")) を
コンパイルしてくれてめでたしめでたし?
今日はここまで
達成!