Let's start Scheme

2012-11-15

ブレインストーミング

考えをまとめるのに紙に書くのが面倒なのと、記録を残しておいた方がいい類のものなのでメモ。

以下のコードを考える。
(read (open-string-input-port "#!fold-case ABC"))
(read (open-string-input-port "ABC")) ;; こいつは何を返すか
恐らく書いている方の期待する結果はABCというシンボルだろう。だが、これは予想に反してabcを返す。(Sagittariusでは#!fold-caseは全部小文字にして返す) 。こんなの書くやついないよ!と思っていたのだが、意外なことに自分がこれではまったのでやはり問題かなと思うようになってきた。

何が問題か?

現状ではSagittariusはファイル単位でリードテーブルの切り替えを行う。っが、上記のようなものだと同一ファイル上でreadが走るのでファイル単位で行うと残念な結果になる。つまり、ファイルではなくポート単位で行う必要があるということになる。

どう直すか?

ポート単位にするというのは結構大掛かりな変更になる。現状の実装では、VMが現在のリードテーブルを保持して、loadが走る際にポートに保存、loadが終わったら保存したテーブルをVMに戻すという割と回りくどいことをしている。
これをポート単位にするにはどうすればいいのか?とりあえず簡単に思いつくのはVMが現在loadしているポートを保存するというもの。loadは必ずファイル単位で行われるので、現在のloadingポートが分かれば何とかなりそうである。となると問題になってくるのは、どの段階でポートにリードテーブルを持たせるかというもの。可能性としてあるのは、
  1. ポート作成時。(全てのポートはリードテーブルを持つ)
  2. リードテーブルを変更するようなものを検出したとき。(オンデマンド)
上記のようなコードだと、2つ目のポートに持たせるのは無駄以外のなんでもないような気がするので、やるならオンデマンドだろうか。

方針は決まったので実装するか。

一通り実装して、テストを走らせたら気づいた(というかテストが失敗した) こと。以下のようなコードがまずい。
#!read-macro=curly-infix
(read (open-string-input-port "{a + b}"))
ポート単位ても、loadingポートで影響を受けたものはその中のreadも影響を受けないと面白くない。修正はそんなに難しくないのだが、問題はパフォーマンスに大きな影響が出るであろうこと。

何故か?この場合だと、ポートを作るたびにリードテーブルをコピーしないといけなくなるからだ。う~ん、うれしくないけどしょうがないか・・・

2012年11月16日 追記
とりあえず0.3.8では親ポート(と便宜上呼ぶ)から子ポート(と便宜上ry)にリードマクロを継承するようにしたけど、これ本当に正しい判断だったのかな?よく考えれば、上記のコードが動いてもうれしいことはあまりないような?
上記のものみたいに「すでに何が読まれるのか見えている」ものは確かにそうあってほしいと思うかもしれないけど、これファイルポートだったらどうなるという話な気がするし、以下のような場合はむしろ邪魔になる気がする;
(define p (opne-file-output-port "something-written-in-sexp"
                                 (file-options no-fail) 'block (native-transcoder))
#!read-macro=curly-infix
(read p) ;; what should happen?
こんなコード書くやついねぇよって考えるとまた自分が嵌る羽目になる気がする。(現状では、read時に毎回親ポートをチェックするので、上記のコードは大方の予想を裏切る結果にはなる。)
上記コードだと子ポート内でリードマクロを読み込んだ場合とかのことを考えると、やはり継承はない方がいい気がする。しまったなぁ、一見そうあるべきのように見えるものの影に隠れた問題の本質を見落とした。非互換な変更がまた起きる・・・

No comments:

Post a Comment