¿Hay un patrón de programación establecida para hacer frente a esto?
Sí, funciones de contenedor que desacoplan la limpieza de recursos del manejo de excepciones. Lo que hago es utilizar un envoltorio genérico, unwind
(un LISPism con la que estoy más acostumbrado):
let unwind ~(protect:'a -> unit) f x =
try let y = f x in protect x; y
with e -> protect x; raise e
Se trata de un simple envoltorio que no lo hace correctamente dar cuenta de las excepciones planteadas en protect
; un envoltorio totalmente comprobado que asegura que protect
se llama una sola vez, aunque él mismo no podría ser Yaron Minski's, o este otro que creo que es un poco más claro:
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
Entonces, defino casos específicos según sea necesario, por ejemplo, :
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)
La razón me cambio de los parámetros de las funciones específicas with_
es que me resulta más conveniente para la programación de orden superior; en particular, mediante la definición de un operador a la aplicación Haskell, puedo escribir:
let() = with_output_file "foo.txt" $ fun otch ->
output_string otch "hello, world";
(* ... *)
con una sintaxis no es muy pesado. Para un ejemplo algo más complicado, considere lo siguiente:
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)
que se puede utilizar con una llamada como with_open_graph " 400x300" $ fun width height -> (*...*)
.
¡Muy bonito! Un poco a un lado: Supongo que '$' se define como 'let ($) f x = f x' pero esto lo haría asociativo izquierdo mientras que es correcto asociativo en Haskell. En consecuencia, 'print_int $ (+) 3 $ 4' no funciona en OCaml. Para un operador de aplicación asociativo correcto, se podría definir 'let (@@) f x = f x'. –
Si 'protect' genera una excepción, ¿se ejecuta dos veces? Porque la excepción se detecta y 'protect' se ejecuta nuevamente. Véase también una [aplicación de Yaron Minsky de 'protect'] (http://caml.inria.fr/pub/ml-archives/caml-list/2003/07/5ff669a9d2be35ec585b536e2e0fc7ca.en.html). –
@ChristianLindig: Sí, mi código original no maneja excepciones dobles. Ver mi edición para una versión "funcional" que creo que mejora en la de Yaron. –