kb84tkhrのブログ

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

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")) を
コンパイルしてくれてめでたしめでたし?

今日はここまで

達成!