OCaml's try .. with
does not offer a finally
clause like Java. It would be useful, though, especially when dealing with side effects. For example, I like to open a file, pass the open file to a function, and close it. In case the function raises an exception I have to catch it in order to have a chance to close the file. This gets increasingly complicated when multiple files are opened and opening itself might fail as well. Is there an established programming pattern to deal with this?
Below is a simple function illustrating the problem. Function f
is applied to a channel which belongs to a file if a path
is provided and stdin
otherwise. Because there is no finally clause, close_in io
appears twice.
let process f = function
| Some path ->
let io = open_in path in
( (try f io with exn -> close_in io; raise exn)
; close_in io
)
| None -> f stdin
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 -> (*...*)
.