具体的な再現コードは以下のようになる。
(import (rnrs) (srfi :39)) (define x (make-parameter 3 (lambda (x) (+ x 3)))) (print (x)) (parameterize ((x 4)) (print (x))) (print (x)) #| 6 7 9 |#SRFI-39的にもR7RS的にも最後の9は6じゃないといけない。原因はすぐに分かったんだけど、問題はどう解決するかという部分。
ちなみに、原因はparameterizeとparameterの実装にある。parameterizeはdynamic-windを使って実装されているのだが、afterのthunkが値をセットしなおす際に保存された値が変換されるという悲しいことがおきる(この場合は6が保存されて、6をセットする際に9に変換される)。 まぁ、解決方法は簡単で、after thunkで保存された値をセットする際に、変換を行わなければいい。
言うは易し、行うは難しの典型である。なぜか、そんなAPIが無いからだ。現状ではパラメタはYpsilonの実装を移植したものを使っている。この実装ではパラメタは単なるlambdaである。つまり、その中身にアクセスする方法などないということだ。(もちろん、同様の問題がYpsilonでも発生する。ちなみにChezでも起きた。意外とこのバグはいろんな実装で穴になっているっぽい。)
とりあえずぱっと思いつく解決方法は2つ。
- パラメタ作成時に直接値を設定できる手続きを作って一緒に保存する
- せっかくobject-applyがあるんだし、CLOSで実装してしまう
2の方法だとパラメタを呼び出すときのオーバヘッドが気になる。なんだかんだで総称関数の呼び出しは現状では重たい。
さて、どうしようかな。
とりあえず、1の方法で直したんだけど、CLOSを使った場合もメリットがあることに気付いた。
現状では、パラメタはVMが持っているハッシュテーブルに値を保存してるんだけど、これ単なるハッシュテーブルだから一度パラメタが作られると消えないのだ。つまり、(あんまり書かれないけど)ローカルにパラメタを束縛するとメモリ使用量が減らないという切ない問題がある。(これは、パラメタをlambdaで実装しているための問題でパラメタが作られた後に登録しなおせばいい気もするが、GCを止めないとうっかり死ぬような気もしていて怖い。)
しかし、CLOSで実装してやればweak hashtableにしても問題が無い気がしている。パフォーマンスにそこまで影響を与えないならCLOSにしてしまっても良いかもしれない。
4 comments:
これGaucheでも昔はまったような。いくつかバリエーションがありました。
https://github.com/shirok/Gauche/commit/3e9cedc57ba8a5a3873628153ace85833b469960
https://github.com/shirok/Gauche/commit/781bb9935ec3d31e427d7f10c9cc7b912688280b
https://github.com/shirok/Gauche/commit/07534e72973331da23e0d55351ff7a88d9bb8d1d
最後のケースってparameterizeの中で設定した値になるのが違和感があるのですが、こういうものなんですかね?(SRFI-39の参照実装でもそうなるのでそうなんでしょうけど・・・)
「パラメータオブジェクト」への参照が置き換わっているので、抜けた後のbは新たなパラメータオブジェクトを参照しているで自然だと思います。(pamrameterizeが値を戻すのは以前のbに指されていたパラメータオブジェクトで、そちらへの参照が別に残っていれば戻った値が見えると。)
set!があるのを見落としてました(^^;
Post a Comment