衝突回避と暗黙のimport

R6RS では import した変数に set! することは出来ない。 明示的な import であろうと暗黙の import であろうと関係なく set! できないといったようなことが 7.1 に書かれている。 マクロ展開の結果が set! になるようなのも駄目だ。

それを回避するためのマクロとして、パラメタで覆う方法を以前に書いた。

http://saito.hatenablog.jp/entry/20100822/1282449327

これは変数に set! するかのようなインターフェイスで、実際には変数が指しているオブジェクトを更新するというアイデアだ。

だが、この方法では実行時の処理が間に入るようになるのが無駄だ。 どうにかしてもっと単純に出来ないかと考えたところ、変数定義と同時にセッタを定義すればいいじゃないかと思い付いた。 それで出来たのがこれだ。

#!r6rs
(library (settable-variable)
  (export define-settable)
  (import (rnrs))
  
  (define-syntax define-settable
    (syntax-rules ()
      ((_ var val)
       (begin
         (define dummy val)
         (define (set-dummy! x) (set! dummy x))
         (define-syntax var
           (make-variable-transformer
            (lambda(x)
              (syntax-case x (set!)
                ((set! _ a) #'(set-dummy! a))
                (_ #'dummy)))))))))
  )

define のかわりにこの define-settable を使えばライブラリを越えて set! が出来るはずだ。 手続を一個経由することにはなるが、この程度なら賢い処理系はインライン化する等して実行時の速度へはほとんど影響しなくなるだろう。

試してみよう。

#!r6rs
(library (test-lib)
  (export var1 var2)
  (import (rnrs) (settable-variable))
  
  (define-settable var1 #f)
  (define-settable var2 #f)
  )
#!r6rs
(import (rnrs) (test-lib))

(set! var1 1)
(set! var2 2)

(display var1)
(display var2)

ところが、この例が動く処理系とエラーになる処理系に分かれてしまった。

動く処理系は

  • Racket
  • Ypsilon

エラーになる処理系は

  • Larceny
  • mosh
  • IronScheme
  • Petite Chez Scheme

その他、 Sagittarius では動くが挙動が異なる。

主要な処理系でこのように分かれてしまうからには何か未定義の部分に引掛っているのではないかと思うけれど、どういう理屈でこうなるのかわからないでいる。

Document ID: 891343cdfc7de14c15e6f1720cf29e15