Is there an established programming pattern to deal with this?
Yes, wrapper functions that decouple resource clean-up from exception handling. What I do is to use a generic wrapper, unwind
(a LISPism with which I am rather used):
let unwind ~(protect:'a -> unit) f x =
try let y = f x in protect x; y
with e -> protect x; raise e
This is a simple wrapper that doesn't correctly account for exceptions raised in protect
; a fully checked wrapper that ensures that protect
is called only once even if it itself fails could be Yaron Minski's, or this one which I think is a bit clearer:
let unwind ~protect f x =
let module E = struct type 'a t = Left of 'a | Right of exn end in
let res = try E.Left (f x) with e -> E.Right e in
let () = protect x in
match res with
| E.Left y -> y
| E.Right e -> raise e
Then, I define specific instances as required, for instance:
let with_input_channel inch f =
unwind ~protect:close_in f inch
let with_output_channel otch f =
unwind ~protect:close_out f otch
let with_input_file fname =
with_input_channel (open_in fname)
let with_output_file fname =
with_output_channel (open_out fname)
The reason I switch the parameters for the specific with_
functions is that I find it more convenient for higher-order programming; in particular, by defining an application operator à la Haskell, I can write:
let () = with_output_file "foo.txt" $ fun otch ->
output_string otch "hello, world";
(* ... *)
with a not very heavy syntax. For a somewhat more involved example, consider the following:
let with_open_graph spec (proc : int -> int -> unit) =
unwind ~protect:Graphics.close_graph (fun () ->
proc (Graphics.size_x ()) (Graphics.size_y ());
ignore (Graphics.wait_next_event [Graphics.Button_down]);
ignore (Graphics.wait_next_event [Graphics.Button_up]))
(Graphics.open_graph spec)
which can be used with a call like with_open_graph " 400x300" $ fun width height -> (*...*)
.
$
is defined aslet ($) f x = f x
but this would make it left associative whereas it is right associative in Haskell. Consequentlyprint_int $ (+) 3 $ 4
does not work in OCaml. For a right associative apply operator one could definelet (@@) f x = f x
. – Unquestioned