Scheme:マクロ:pset!

Scheme:マクロ:pset!

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) ...)))))))
More ...