RacketのMacroを調べてみる (9)
今回はanaphoric ifの話から
> (aif (big-long-calculation)
(foo it)
#f)
って書くと、(big-long-calculation)
が#f
のときは当然#f
を返しますが
そうでないときは(big-long-calculation)
の結果をit
が覚えててくれる、というもの
普通はlet
でいったん変数に入れておくなどしますがその手間が省けると
で、なにも考えずにこう書くと
(define-syntax-rule (aif condition true-expr false-expr)
(let ([it condition])
(if it true-expr false-expr)))
define-syntax-rule
の中のit
と、(foo it)
のit
が
別物としてあつかわれてしまい、想定した動きになりません
ここでSyntax Parameterの登場です
そもそもRacketにはParameterってやつがありまして
ものすごくざっくりいうと、少しお行儀のいいグローバル変数くらいのものです
ちょっと一時的に値を変更したりすることが安全にできます
でSyntax ParameterっていうのはそのSyntax版
ではaifの定義を見ていってみます
(require racket/stxparam)
Syntax Parameter関連はstxparamモジュールに入っているのでrequireします
(define-syntax-parameter it
(lambda (stx)
(raise-syntax-error (syntax-e stx) "can only be used inside aif")))
it
という名前のSyntax Parameterを定義します
なにもなしで使うとエラーを出すようにしています
(define-syntax-rule (aif condition true-expr false-expr)
(let ([tmp condition])
いったん計算してlet
でいったんtmp
という変数に入れます
(if tmp
(syntax-parameterize ([it (make-rename-transformer #'tmp)])
true-expr)
false-expr)))
make-rename-transformer
は、他の識別子名を読み替えてしまうtransformerを作りますsyntax-parameterize
は一時的にSyntax Parameterの値を変更します
スコープを抜けると自動的に元の値に戻ります
というわけで、true-expr
を評価している間だけit
という識別子がtmp
に読み替えられます
というわけでaif
のできあがり
次は splicing-let の話です
Scheme修行にはこんな書き方が出てきてました
yを隠すことができます
(define get-y
(let ([y 0])
(lambda () y)))
Lispのイディオムで、Let over lambdaというそうです
その代わりにこう書くことができます
(require racket/splicing)
(splicing-let ([x 0])
(define (get-x) x))
で?と言いたくなりますが
定義したい関数が複数になると、こんな風に書いてたのが
(define-values (inc dec get)
(let ([x 0])
(values (lambda () (set! x (+ x 1)))
(lambda () (set! x (- x 1)))
(lambda () x))))
こう書けて楽ちん、ということのようです
(splicing-let ([x 0])
(define (inc) (set! x (+ x 1)))
(define (dec) (set! x (- x 1)))
(define (get) x))
この間作ったstruct
の例ではdefine
がいくつも出てきてましたがbegin
で囲まれてました
状態を持たせてdefine
を複数書きたいときには重宝する、ってことでしょうか
scheme修行のときはマクロなんてないので、
複数の関数を定義する代わりに
複数の関数を返すひとつの関数を作ってましたね
あれもなんとなくカッコよくて好きですが
spliceっていうのはschemeを勉強しだして始めて知った単語で
「皮をむく」という意味です`(1 2 ,(list 3 4))
は(1 2 (3 4))
になりますが`(1 2 ,@(list 3 4))
は(1 2 3 4)
になります,
はunquote
の略記ですが,@
はunquote-splicing
の略
確かに皮がむけてます
splicing-let
とunquote-splicingは直接的な類似はないように思えますが
上の例でsplicing-let
の代わりにlet
を使ってしまうとdefine
が隠されてしまいますから、皮がむけてると言えますね
達成!