kb84tkhrのブログ

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

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が隠されてしまいますから、皮がむけてると言えますね

 

達成!