Shiro(2010/05/25 01:48:28 PDT): Common LispのpsetqをSchemeでどう書く、という話が Chaton/twitter方面で出て、ちらっと書いたんだけど、 後で役に立つかもしれないのでこっちにも写しておく。
psetqは複数の代入を「並列に」行う。並列っていっても並列計算じゃなくて、 それぞれの代入が他の代入に影響を与えないってこと。
(let ((x 0)) (y 1)) (psetq x y y x) (list x y)) => (1 0)
普通にsetfを並べるだけだと最初のx <- y の代入でxが上書きされちゃうからね。
この機能だけならR5RSマクロで書ける (エッセンスを示すためにchatonに書いたのより さらに簡略化した)。
(define-syntax pset! (syntax-rules () [(_ v0 e0 more ...) (pset!-aux (v0) (e0) (t0) (more ...))])) (define-syntax pset!-aux (syntax-rules () [(_ (vs ...) (es ...) (ts ...) ()) (begin (let ((ts es) ...) (set! vs ts) ...))] [(_ (vs ...) (es ...) (ts ...) (v e more ...)) (pset!-aux (vs ... v) (es ... e) (ts ... t) (more ...))]))
やりたいのは、
(pset! v0 e0 v1 e1 v2 e2)
これを、
(let ((t0 e0) (t1 e1) (t2 e2)) (set! v0 t0) (set! v1 t1) (set! v2 t2))
こう変換すること。Lispのlegacy macroならここで「一時変数をgensymして mapして…」と考えちゃうんだけど、R5RSマクロはちと面倒。 pset!-auxという補助マクロで再帰しながら、vとeを取り出して一時変数tを積んでいってる。
こういうスタイルが良いかどうかはScheme界でも賛否両論ある。 まあ個人的には、元のフォームにmapかけたくなったらもうその時点で R5RSマクロはふさわしい道具じゃ無いだろうなと思う。 出来なくはないってだけで。
なおGaucheで展開結果を見るのはちょっと不便。普通にmacroexpandすると、 シンボルがidentifierに置き換えられたものが得られる。
gosh> (macroexpand '(pset! v0 e0 v1 e1 v2 e2)) (#<identifier user#begin> (#<identifier user#let> ((#0=#<identifier user#t0> e0) (#2=#<identifier user#t> e1) (#3=#<identifier user#t> e2)) (#1=#<identifier user#set!> v0 #0#) (#1# v1 #2#) (#1# v2 #3#)))
unwrap-syntaxするとまともなS式になるけど、一時変数の名前が同じだから 変数衝突が起きてるんじゃないのか? と思ってしまう (実際は、同じtでも 上の結果でわかるように実体が別のidentifierなんだけど)。
gosh> (unwrap-syntax (macroexpand '(pset! v0 e0 v1 e1 v2 e2))) (begin (let ((t0 e0) (t e1) (t e2)) (set! v0 t0) (set! v1 t) (set! v2 t)))
このへんの不便さはそのうちなんとかしたい。
R6RSマクロのsyntax-caseによる実装は読者への課題としておく、 と書いとけばだれかやってくれるに違いない。
齊藤(2010/05/25 06:33:23 PDT): やってみました。 R5RS より簡潔に書けなきゃ甲斐がないと思って頑張ってみたんですが、まだ改善の余地は残ってそうな予感。
(define-syntax pset! (lambda(x) (define (group x) (syntax-case x () (() #'()) ((v e rest ...) #`((v e) . #,(group #'(rest ...)))))) (syntax-case x () ((_ v1 e1 rest ...) (with-syntax ((((vs es) ...) (group #'(v1 e1 rest ...)))) (with-syntax (((ts ...) (generate-temporaries #'(vs ...)))) #'(let ((ts es) ...) (set! vs ts) ...)))))))