#datum->syntax
の嵌りどころ
R6RSにはdatum->syntax
という手続きがある。これを使えばaif
等のsyntax-rules
のみでは書くことが難しいマクロ1も書くことができる。例えばaif
は以下のように書ける。
#!r6rs
(import (rnrs))
(define-syntax aif
(lambda (x)
(syntax-case x ()
((aif test then)
#'(aif test then #f))
((aif test then else)
(with-syntax ((it (datum->syntax #'aif 'it)))
#'(let ((it test))
(if it then else)))))))
(aif (assq 'a '((a . 0) (b . 1))) it #f)
;; -> (a . 0)
ここまでならば嵌りどころはないように思えるが、上記の定義を以下のようにすると問題が起きる。
#!r6rs
(import (rnrs))
(define-syntax aif
(lambda (x)
(syntax-case x ()
((_ test then) ;; here is got changed
#'(aif test then #f))
((aif test then else)
(with-syntax ((it (datum->syntax #'aif 'it)))
#'(let ((it test))
(if it then else)))))))
(aif (assq 'a '((a . 0) (b . 1))) it) ;; use the first pattern
;; error unbound variable it
これはdatum->syntax
でテンプレートとして受け取る識別子が最初のパターンで生成されたテンプレート変数になるため、本来の構文情報を取得できないからである。これを回避するためには最初に示したように_
を使わずに何かしらのパターン変数で置換してやる必要がある。
この嵌りどころはsyntax-case
のみを使っているのであれば問題はないのだが、syntax-rules
と組み合わせると意外なところで嵌ることがある。例えば以下のようなコードを考えてみる。
#!r6rs
(import (rnrs))
(define-syntax aif
(lambda (x)
(syntax-case x ()
((k test then else)
(with-syntax ((it (datum->syntax #'k 'it)))
#'(let ((it test))
(if it then else)))))))
(define-syntax wrapped-aif
(syntax-rules ()
((_ test then else)
(aif test then else))))
(wrapped-aif (assq 'a '((a . 0) (b . 1))) it #f)
;; -> error unbound variable it
一見特に問題がないように見えるのだが_it_はaif
の構文情報で生成されるのでwrapped-aif
からは参照することができない。解決方法は読者への宿題とすることにする23。
#駄文
ここからは駄文である。関数型ポエムを大量に書いていた人の処分についてであり、特に読む必要はないので、適当に読み飛ばしていただきたい。
全てを観測したわけでもないので事実と反する部分があるかもしれないが、結果としてポエムを大量生成していたかの氏はアカウント凍結という処分になったようである。この決定について一点だけ懸念がある。氏のアカウント凍結は、他者への誹謗、中傷を行ったためという理由であってほしいということだ。多くの人が技術的に間違いであり、指摘してもそれが直ることがないということを気にしていたようであるが、それが理由での凍結であれば、Qiitaというサービスは学者もしくはそれに類する知識を有する人々のみが何かを書くことを許されたサービスということになる。それが悪いというわけではないが、誤りを含む記事を書くこと=アカウント凍結の処分ではいささか厳しすぎるように思える。
悪貨が良貨を駆逐するという事象であったことに異論を唱えるつもりはないが、もしこの決定について単に識者の勝利だと思っている方がいるのであれば、大変危険ではないかと思う。