Let's start Scheme

2012-10-15

Portは閉じられるべきか?

デバッグをしている最中に「随分dynamic-winderがあるなぁ」ということに気づいた。スクリプトを眺めると、特にdynamic-windもguardも使われていない。あるのは、call-with-output-bytevector-portのみ。

そういえば、この辺の手続きはなぜかdynamic-windを使ってportを閉じているなぁと言うことを思い出し、R6RSではどう規定されてたっけと仕様書を眺める。あれ?何も書いてない。 call-with-portでは明示的にportは閉じられないと書いてあるが、勝手に作られる、しかも中身をR6RSの範囲では取り出しようがないものは手を抜かれたのだろうか?

じゃあ、別に閉じる必要ないよね。とは言っても、他の処理系の挙動と一応整合性を取っておきたい、ということで簡易テスト。
(import (rnrs))
(define p #f)

(guard (e (else (put-bytevector p #vu8(1 2))))
  (call-with-bytevector-output-port
   (lambda (port)
     (set! p port)
     (error 'ghehe "gehehe"))))
こんなの書いて、エラー投げなかったら閉じてない、投げたら閉じてるという感じで。

【結果】
閉じてる処理系:Ypsilon
閉じてない処理系:Chez、Racket(plt-r6rs)、Larceny、Mosh

閉じない方がメジャーな振る舞いっぽいし、call-with-portとの一貫性も取れるので閉じない方向で。

3 comments:

shiro said...

dynamic-windでポートを「閉じてはいけない」のは書く必要もなく明白です。call/ccを使ったコルーチンで、call-with-*-file の中で実行をサスペンドしてその外側で作られた継続に実行を移したあと、サスペンドした実行を最再開できる必要があるからです。

call-with-*-file中でエラーが投げられた場合については、 "If proc does not return, the port is not closed automatically, unless it is possible to prove that the port will never again be used for an I/O operation" ですが、これには二つの立場があり得ると思います。

(1) call-with-*-file中で、エラーが生じる前に捕捉された継続が再実行される場合があり得る。その時にポートに触る可能性があるから、閉じられない。

(2) エラーで脱出した時点で call-with-*-file の動的環境は無効になっており、それを使おうとするコードは不正である。不正なコードを実行したんだから何が起きてもいいよね。ポートが閉じられちゃったとかでも。

後者はr6rsでも明確に書いてないと思うんで、規格を字義通り解釈するなら(1)ですかね。でもエラーで脱出しちゃったらそれを捕まえたguard以下の環境に一貫性があるかどうかわからないわけで、(2)を完全に否定するのはあまり意味がないだろうなと私は思います。

kei said...

call/ccの存在をすっかり忘れてました。proc内で束縛された継続をエラーの後に使っても全く問題がないんでした。

(2)を肯定すると、Schemeの仕様でunwind-protectが書けるような気がします。気がするだけですが・・・

shiro said...

Gaucheは(2)の立場でunwind-protectを提供しています。

Post a Comment