Let's start Scheme

2015-11-19

R7RSのライブラリに関する疑問

R7RSを実装する際に、ライブラリ周りはR6RSを使いまわしにできたのであまりその仕様について深く考察したことがなかったのだが、最近ちょっと考えることがあり疑問というか不明瞭な点がいくつかあることに気付いた。具体的にはライブラリは複数回読み込まれる可能性があるというこの仕様が不明瞭な点を作っている。仕様から用意に読み取れる点から、ちょっと突っ込んだ(ら直ぐに不明瞭になる)点をだらだらと書いてみようと思う。

明示的に書いてある点

ライブラリAは複数のプログラム、ライブラリから読み込まれた際には読み込みが複数回に渡ることがある。
これは非常に簡単で、以下のようなものになる。
(define-library (A)
  (export inc!)
  (import (scheme base) (scheme write))
  (begin 
    (define inc! 
      (let ((count 0)) 
        (lambda () 
          (set! count (+ count 1))
          count)))
   )
)

(define-library (B)
  (export count-B)
  (import (scheme base) (A))
  (begin (define count-B (inc!)))
)

(define-library (C)
  (export count-C)
  (import (scheme base) (A))
  (begin (define count-C (inc!)))
)

(import (B) (C))

count-B
;; -> 1

count-C
;; -> 1 or 2
ライブラリBがインポートされるのが先と仮定すると、count-Cの値は未定義である。これはライブラリAが複数回評価される可能性があるからであり、これは明示的に書いてある。

書いてない点

いっぱいあるんだけど、とりあえず以下。
(import (scheme eval))

;; library (A) is the same as above
(eval '(inc!) (environment '(A)))
;; -> 1

(eval '(inc!) (environment '(A)))
;; -> ???
これ微妙に書いてない気がする。これは多分、2を返すのが正しい(はず)。根拠としては§5.6.1の最後にあるこれ:
Regardless of the number of times that a library is loaded, each program or library that imports bindings from a library must do so from a single loading of that library, regardless of the number of import declarations in which it appears. That is, (import (only (foo) a)) followed by (import (only (foo) b)) has the same effect as (import (only (foo) a b)).
environmentの引数はimport specである必要があるので、無理やり読めば上記に該当するような気がする。ついでに、プログラムの定義が一つ以上のimport句と式と定義されてる、かつライブラリが複数回読まれる可能性は、読み込みが複数のプログラムもしくはライブラリからなので。

気にしてるのはこれ
(import (scheme base) (prefix (scheme base) scheme:))
一つのプログラム内だから一回のみかなぁとは思うんだけど、微妙に読み取り辛い気がする。

これはどうなるんだろう?
;; a.scm
(import (A))
(inc!)

;; other file
(import (scheme load))
(load "a.scm")
(load "a.scm")
loadは同一の環境で評価するけど、二つのプログラムになるから、両方とも1を返してもいいのかな?それとも、loadで読み込まれたファイルは呼び出し元と同じプログラムということになるのだろうか?

まぁ、こんなことを考えているんだけど、実際の処理系で複数回ライブラリを評価するのは見たことがないので「完全処理系依存フリー」とかいうことをしなければ気にすることはないのではあるが。

2 comments:

Shiro Kawai said...

ライブラリが状態を持ったらポータブルではない、とだけ解釈するしかないと思います。実用上は状態を持たせることが便利であるケースは多いのですが。

似たような落とし穴はプログラムを分割開発する場合にどうしても出てくることで、例えば http://blog.practical-scheme.net/shiro/20100803-static-member-in-header なんかも本質的には同じ問題だと思います。

(プログラムのモジュール分割とモジュールが状態を持つことについては70年代だか80年代くらいに色々考察されていたような気がするんですが手元にポインタがありません。)

ライブラリインスタンスは必ずひとつ、ということを強制しようとすると、コンパイルやリンクを含むツールチェインに渡って考えなければならず、RnRSでは手に余るということでしょう。実用指向なら何らかの規定が欲しいところではありますが、それをRnRSでやるべきか、別の規格でやるべきかは議論のあるところだと思います。

kei said...

なるほど、確かにそう解釈するのが一番っぽいですね > ライブラリが状態を持ったらポータブルではない

気になるのは状態を持たなくてもポータブルじゃない場合がある点ですね。例えばSRFI-1のfirstをリネームエクスポートしてプログラムAで(scheme base)のcarと比較した場合に同一の束縛であることが保証されないとか。これも、ライブラリ跨いだら束縛が同一であることは保証されないと考えるしかなさそうですけど。

ツールチェインのところまでは考えが及びませんでした。確かにRnRSで規定するには荷が重いかなぁと思いますね。別の規格でしてしまうと、それが例えSRFIであっても、追従するかしないかは処理系任せになってしまうので、難しそうな問題です。(R6RSでは規定してた気がしたのですが、読み返してみると何処にも(少なくとも明示的に)書いてないような気がするので気の所為だったのかなぁ?)

Post a Comment