Scheme:マクロ:anaphoric ifの代替
OnLispに出てくるanaphoric ifの代わりになる一般的な技、ありませんか? non-hygienicなマクロが気持ち悪い(勝手に"it"が束縛されているのはよくな い)というのは分かるんですが、じゃあどうするかというと、aifを使うのに比 べて冗長な書き方をするしかないような気がします。
場合によっては、condの"=>"構文が、aifの代わりに使えます。典型的には、 連想リストに値があればその値を返し、なければ#fを返すというような場合。 ローカル変数を省くことができます。
(cond ((assoc "foo" alist) => cdr) (else #f))
しかし、condを使えばいつでも簡潔に書けるというわけではありません。上の 例では、cdrの代わりにそこに複雑な手続きを置こうとすると、lambdaを書か なければならず、束縛フォームが現れて、見た目に複雑になってしまいます。 うまくいけばcutが使えますが、一般にそうとは限りません。
たとえば、ハッシュテーブルに値があれば、その値とそれを大文字にした値の リストを返し、ハッシュテーブルに値がなければ'()を返す手続きは、次のよ うに書くことになるでしょう。
;;; 書き方(1) (cond ((hash-table-get ht "key" #f) => (lambda (val) (list val (string-upcase val)))) (else '())) ;;; 書き方(2) (let1 val (hash-table-get ht "key" #f) (if val (list val (string-upcase val)) '()))
上記の書き方に比べると、下記のanaphoric ifを使った書き方のほうが明解で 簡単です。
(aif (hash-table-get ht "key" #f) (list it (string-upcase it)) '())
aifと同じくらい簡潔に書けて、なおかつ暗黙のitを使わなくて良いような方 法、ありませんか?
- Shiro: 私はand-let*を良く使います。
(or (and-let* ((val (hash-table-get ht "key" #f))) (list val (string-upcase val))) '())
条件不成立時に返す値が#fであればorが省けます。 また、このパターンが頻出するならばマクロにするでしょう:(define-syntax if-let1 (syntax-rules () ((_ var test then else) (or (and-let* ((var test)) then) else)))) (if-let1 it (hash-table-get ht "key" #f) (list it (string-upcase it)) '())
- なるほど。if-let1というのはいい名前ですね。aifだけではなく、anaphoric and/or/condに対応して、and-let1、or-let1、cond-let1というのを定義できるかも。
- ひらっち: 一般化してみました。condには対応してないけどcaseならいけますね。
(define-syntax anaphoric (syntax-rules () ((_ syn var expr body ...) (let1 var expr (syn var body ...))))) (display (anaphoric and it (memq 1 '(1 2 3)) it)) (newline) (display (anaphoric and it (memq #f '(1 2 3)) it)) (newline) (display (anaphoric if it (= 1 0) it "not equal")) (newline) (display (anaphoric if it (= 1 1) it "not equal")) (newline)
- aifを使いたい理由は心理的なものではないかと思ってきました。数行だけのスコープの 変数を使いたいのだけど、それに適切な名前をつけられないとき、aifを使いたくなるような気がします。つまり、ちゃんとした名前をつけられなくて、「ソレだよ、ソレ!」と言いたいのです。たぶん。aifと比べてif-let1を使うのには抵抗を感じるのですが、それはif-let1ではitを明示的に使っているからで、「ちゃんと名前をつけていないこと」がはっきりして、それにちょっぴりの罪悪感を覚えるからかも。non-hygienicだから抵抗感が少ないというと逆説的ですが。
- ひらっち: 名前を付けなくしてみた。かなり変態的、穴holic...ゲフンゲフン。
(define-macro anaphoric (lambda (syn expr . body) (let1 it (gensym) `(let1 ,it ,expr (,syn ,it ,@(let rec ((body body)) (cond ((eq? body expr) it) ((pair? body) (cons (rec (car body)) (rec (cdr body)))) (else body)))))))) (anaphoric if #0=(memq 1 '(1 2 3)) (display #0#) (display "no menber")) (newline) (anaphoric if #0=(memq 0 '(1 2 3)) (display #0#) (display "no menber")) (newline)
まあ、戯言なんだけどね。
- Shiro: それは黒魔術ですな>ひらっちさん。
「おおげさな名前をつけたくない」という心理はわかります。 プログラマの心理としては、暗黙の束縛って案外フィットするもんで、 Perl使ってる時なんかは$_が結構心地よかったりもすします。 一方Schemeは、「組み立てたものはちょっとやそっとでは壊れない」 という方向を目指します。たとえばaifをこんなふうに使った場合:(aif expr (a-macro-somebody-wrote ... it ...))
ここのitはa-macro-somebody-wroteの書き方によって意味が変わって しまうわけで、そういう危うさを排除したい、という頑固さがSchemeには 感じられますね。