読者です 読者をやめる 読者になる 読者になる

pandoric マクロ

Let Over Lambda という本で pandoric マクロというものが紹介されているのだという。 手続き内で使う変数を外側から制御しようとするものだ。 それを Scheme (chicken scheme) に移植しようとする記事を読んだ。

http://bis83gb.hatenadiary.jp/entry/2015/08/08/141204

Scheme ではこのような目的には parameterize を使うのが一般的であると思うが、 pandoric マクロではパラメタのスコープをより狭い範囲に押込めることが出来るので、用途によってはよりよい設計に使える可能性はあるかもしれない。

しかし、値の代入や参照を case で場合分けするのが不格好に思えたので、なるべくマクロ展開時にディスパッチする方向で設計してみようと考えて出来たのが以下だ。 R6RS 用に書いてある。

#!r6rs
(library (pandoric)
  (export define-pandoric-procedure pandoric-ref pandoric-set!)
  (import (rnrs))

  (begin
    (define-syntax flag-a (syntax-rules ()))
    (define-syntax flag-m (syntax-rules ()))

    (define-syntax %dpp
      (syntax-rules ()
        ((_ name (var ...) (form ...) () ()
            (a ...) (m ...) args body0 body1 ...)
         (begin
           (define a) ...
           (define m) ...
           (define temp)
           (let ((var form) ...)
             (set! a (lambda() var)) ...
             (set! m (lambda(n) (set! var n))) ...
             (set! temp (lambda args body0 body1 ...)))
           (define-syntax name
             (syntax-rules (flag-a flag-m var ...)
               ((_ flag-a var) a) ...
               ((_ flag-m var) m) ...
               ((_ . n) (temp . n))))))
        ((_ name (var ...) (form ...) (v vs ...) (f fs ...)
            (a ...) (m ...) args body0 body1 ...)
         (%dpp name (var ...) (form ...) (vs ...) (fs ...)
               (a ... t1) (m ... t2) args body0 body1 ...))))

    (define-syntax define-pandoric-procedure
      (syntax-rules ()
        ((_ (name . args) ((var form) ...) body0 body1 ...)
         (%dpp name (var ...) (form ...) (var ...) (form ...)
               () () args body0 body1 ...))))

    (define-syntax pandoric-ref
      (syntax-rules ()
        ((_ pandora var) ((pandora flag-a var)))))

    (define-syntax pandoric-set!
      (syntax-rules ()
        ((_ pandora var obj) ((pandora flag-m var) obj))))
    ))

以下のように使う。

#!r6rs
(import (rnrs) (pandoric))

(define-pandoric-procedure (pandtest n)
    ((acc 0))
  (set! acc (+ n acc))
  acc)

(display (pandtest 1))
(display (pandtest 2))
(display (pandoric-ref pandtest acc))
(pandoric-set! pandtest acc 0)
(display (pandtest 1))
(display (pandoric-ref pandtest acc))

もし存在しないパラメータを指定していればマクロ展開時にエラーとなって検出されるので、実行してからエラーになるより良いだろう。

ちなみに R6RS では一時変数 (と言ってもここではトップレベル変数になるのでそう呼んでいいかどうかわからないが) を生成するために generate-temporaries という手続きがあるので、一時変数の生成のために再帰的な処理をする必要は本当はないのだが、当初は R7RS 用に書こうとしていたためにその名残りがある。 R7RS 向けにするのをやめたのは、厳密に R7RS 内でポータブルに書くのが難しかったからだ。 マクロでトップレベル変数を作ったときにリネームされるかどうかが処理系によって解釈が分かれていて、同じ挙動にならない。

practical-scheme の WiLiKi でそのあたりの議論があるので参考になる。 (これは R5RS を前提にした議論だが、 R7RS でも同じといって良いだろう。)

http://practical-scheme.net/wiliki/wiliki.cgi?Scheme%3A%E5%88%9D%E5%BF%83%E8%80%85%E3%81%AE%E8%B3%AA%E5%95%8F%E7%AE%B1%3Alog00#H-11dj7n5q4m01b

リネームされるという解釈をとっている処理系であれば R7RS 処理系でも上述のマクロが動くのだが、 chicken scheme はリネームされないという解釈なのでうまく動かない。 ポータブルに書くのは難しいことだ。

Document ID: eb6d5e9644acffbb54463a22da642098