問題

あんまり問題とは言えないかもしれないが、 (proc port) 内でエラーが起きた場合、ポートを閉じる手続きが呼ばれない。

助けてドラえもん！！

とはいえ、プログラムが終了する時に全てのポートは自動的に閉じられるとかどこかに書いてあった気がするので、 そんなに問題にはならないかもしれないんだけど、Ruby で言うような File.open :

class File def open (path, mode = " r " , perm = 066 ) f = File .new( path, mode, perm ) yield f ensure f.close end end

と同じようなことをしたくなった時に困るので、考えてみたいと思う。

検証

Gauche においての call-with-input-file

試しに Gauche の call-with-input-file を見てみる:

( define-in-module scheme ( call-with-input-file filename proc . flags ) ( let1 port ( apply open-input-file filename flags ) ( unwind-protect ( proc port ) ( when port ( close-input-port port )))))

これは:

( define ( call-with-input-file filename proc . flags ) ( let1 port ( apply open-input-file filename flags ) ( unwind-protect ( proc port ) ( when port ( close-input-port port )))))

と一緒。

unwind-protect というなんかすごそうなシンタックス？で守られてる。 でも Guile にはそんなものはない。

Guile においての call-with-input-file

Guile においての call-with-input-file は 2 つある。 1 つ目は R5RS の call-with-input-file だけど:

( define* ( call-with-input-file file proc #:key ( binary #f ) ( encoding #f ) ( guess-encoding #f )) "PROC should be a procedure of one argument, and FILE should be a string naming a file. The file must already exist. These procedures call PROC with one argument: the port obtained by opening the named file for input or output. If the file cannot be opened, an error is signalled. If the procedure returns, then the port is closed automatically and the values yielded by the procedure are returned. If the procedure does not return, then the port will not be closed automatically unless it is possible to prove that the port will never again be used for a read or write operation." ( let (( p ( open-input-file file #:binary binary #:encoding encoding #:guess-encoding guess-encoding ))) ( call-with-values ( lambda () ( proc p )) ( lambda vals ( close-input-port p ) ( apply values vals )))))

2 つ目は R6RS の call-with-input-file で:

( define ( call-with-input-file filename proc ) ( call-with-port ( open-file-input-port filename ) proc ))

便利な call-with-port が付いて………:

( define ( call-with-port port proc ) "Call @var{proc}, passing it @var{port} and closing @var{port} upon exit of @var{proc}. Return the return values of @var{proc}." ( call-with-values ( lambda () ( proc port )) ( lambda vals ( close-port port ) ( apply values vals ))))

やっぱ、ダメじゃねぇか！！！

やっぱり call-with-values 使ってんじゃねーかァァァァァァァァァァァァァァ！！！！！！！！！！！！！！！

マジでそうなの？

Guile で call-with-values を使ってるけど、本当に後の処理(ポートを閉じる処理)が呼ばれないのか検証してみた。

( define error-happen? #t ) ( call-with-values ( lambda () ( when error-happen? ( error "*Oops!* teleporter" )) ( values 4 5 )) ( lambda ( a b ) ( display "handler" ) ( newline )))

上記のコードを demo-call-with-values.scm に保存して実行してみた結果が以下である:

% guile ./demo-call-with-values.scm Backtrace: In ice-9/boot-9.scm: 160: 7 [catch #t #<catch-closure 1cac460> ...] In unknown file: ?: 6 [apply-smob/1 #<catch-closure 1cac460>] In ice-9/boot-9.scm: 66: 5 [call-with-prompt prompt0 ...] In ice-9/eval.scm: 432: 4 [eval # #] In ice-9/boot-9.scm: 2404: 3 [save-module-excursion #<procedure 1cce9c0 at ice-9/boot-9.scm:4051:3 ()>] 4058: 2 [#<procedure 1cce9c0 at ice-9/boot-9.scm:4051:3 ()>] In /home/rihine/workspace/guile-cwv/./demo-call-with-values.scm: 10: 1 [#<procedure 1d9ebc0 ()>] In unknown file: ?: 0 [scm-error misc-error #f "~A" ("*Oops!* teleporter") #f] ERROR: In procedure scm-error: ERROR: *Oops!* teleporter

上記の通り、 handler とは出力されていない(そりゃそーだ)。 替わりにテレポーターに引っかかってしまった。

解決法みたいなもの

すごく簡単な解決方法として、 Guile で unwind-protect を実装するというものが考えられる。

あるいは、 finally のようなものを継続を使って実装する、とか。