Emulating try-with-finally in OCaml
Asked Answered
U

7

10

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
Unquestioned answered 30/6, 2012 at 19:23 Comment(0)
G
9

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 -> (*...*).

Goar answered 30/6, 2012 at 22:31 Comment(4)
Very nice! A little aside: I assume $ is defined as let ($) f x = f x but this would make it left associative whereas it is right associative in Haskell. Consequently print_int $ (+) 3 $ 4 does not work in OCaml. For a right associative apply operator one could define let (@@) f x = f x.Unquestioned
If protect raises an exception, is it executed twice? Because the exception is caught and protect is executed again. See also a Yaron Minsky's implementation of protect.Unquestioned
@ChristianLindig: Yes, my original code doesn't handle double exceptions. See my edit for a "functional" version that I think improves on Yaron's.Goar
NB extlib has Std.finally (it makes another choice for which exception to raise when cleanup blows).Tumble
A
7

As of OCaml 4.08, there is a Fun.protect function which provides this functionality in the standard library.

Using it, your example would look like:

let process f  = function 
  | Some path ->
      let io = open_in path in
      Fun.protect (fun () -> f io)
        ~finally:(fun () -> close_in io)
  | None -> f stdin
Acaricide answered 6/5, 2021 at 22:24 Comment(1)
Fun.protect can be used to define a defer function similar to the one in Go: let defer f = Fun.protect ~finally:f now you can use: let io = open_in path in defer (fun () -> close_in io) @@ f io such that any closing action is defined right after the opening and the rest of the function never needs to worry about it.Unquestioned
H
3

From the book Unix system programming in OCaml by Xavier Leroy and Didier Rémy in the chapter Generalities

"There is no built-in finalize construct try …finalize in the OCaml language, but it can be easily defined: "

let try_finalize f x finally y =
  let res = try f x with exn -> finally y; raise exn in
  finally y;
  res
Hamitosemitic answered 6/4, 2013 at 21:45 Comment(0)
G
2

It is not built-in to OCaml as far as I know, but you can write a library that lets you encode such patterns. An example library is Catch me if you can, which is a monadic encoding of errors. Here is a tutorial for adding a finally construct using metaprogramming. There are probably others approaches as well.

Garrow answered 30/6, 2012 at 19:31 Comment(1)
Thanks for the suggestions. I was hoping for a smart solution within the core language rather than relying on Camlp5.Unquestioned
C
1

Here is an option. (I have removed the matching on path to boil the code down to a minimal example.)

let process f path =
  let exn = ref None in
  let io = open_in path in
  (try f io with e -> exn := Some e);
  close_in io;
  match !exn with Some e -> raise e | None -> ()
Couple answered 30/6, 2012 at 20:15 Comment(1)
The idea here is to store the exception to avoid duplicating code that would go into a final block. An alternative design would be to capture the final block in a local function and to call it from two places.Unquestioned
A
1

The OCaml Batteries library collection offers two functions that can be used for finally

http://ocaml-batteries-team.github.io/batteries-included/hdoc/BatPervasives.html

val finally : (unit -> unit) -> ('a -> 'b) -> 'a -> 'b

finally fend f x calls f x and then fend() even if f x raised an exception.

val with_dispose : dispose:('a -> unit) -> ('a -> 'b) -> 'a -> 'b

with_dispose dispose f x invokes f on x, calling dispose x when f terminates (either with a return value or an exception).

Assuan answered 5/11, 2013 at 12:23 Comment(0)
P
1

If both the function and the finally block raises an exception I prefer to see the initial exception, so I use something like this:

type 'a result = OK of 'a | Exn of exn
let result_of f x = try OK (f x) with e -> Exn e

(** invokes [f x], and always releases [x] by invoking [release x] *)
let do_with x release f =
  let result = result_of f x in
  let closed = result_of release x in
  match result, closed with
  | Exn e, _ -> raise e (* [f x] raised exception *)
  | _, Exn e -> raise e (* [release x] raised exception *)
  | OK r, OK () -> r (* all OK *)
Peptone answered 5/11, 2013 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.