OCaml的try .. with
不提供像Java这样的finally
子句。但是,这将是有用的,特别是在处理副作用时。例如,我想打开一个文件,将打开的文件传递给一个函数,然后关闭它。如果函数引发异常,我必须捕获它以便有机会关闭文件。当打开多个文件并且打开自身也可能失败时,这变得越来越复杂。是否有一个既定的编程模式来处理这个问题?
以下是说明问题的简单功能。如果提供f
,则函数path
将应用于属于文件的频道,否则将应用stdin
。由于没有finally子句,close_in io
出现两次。
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
答案 0 :(得分:7)
是否有既定的编程模式来解决这个问题?
是的,将资源清理与异常处理分离的包装函数。我所做的是使用通用包装器unwind
(我更喜欢使用的LISPism):
let unwind ~(protect:'a -> unit) f x =
try let y = f x in protect x; y
with e -> protect x; raise e
这是一个简单的包装器, 无法正确解释protect
中引发的异常;一个完全检查的包装器,确保protect
只被调用一次,即使它本身失败也可能是Yaron Minski's,或者我觉得这个更清晰一点:
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
然后,我根据需要定义特定实例,例如:
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)
我切换特定with_
函数的参数的原因是我发现它对于高阶编程更方便;特别是,通过定义应用程序运算符la Haskell,我可以写:
let () = with_output_file "foo.txt" $ fun otch ->
output_string otch "hello, world";
(* ... *)
语法不是很重。有关更为复杂的示例,请考虑以下事项:
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)
可以与with_open_graph " 400x300" $ fun width height -> (*...*)
之类的调用一起使用。
答案 1 :(得分:3)
Xavier Leroy和DidierRémy在Unix system programming in OCaml一章中的Generalities一书
“没有内置的finalize构造尝试...在OCaml语言中完成,但可以很容易地定义:”
let try_finalize f x finally y =
let res = try f x with exn -> finally y; raise exn in
finally y;
res
答案 2 :(得分:2)
据我所知,它不是内置于OCaml,但您可以编写一个可以编码此类模式的库。示例库是Catch me if you can,它是一个错误的monadic编码。 Here是使用元编程添加finally
构造的教程。也可能有其他方法。
答案 3 :(得分:2)
答案 4 :(得分:1)
这是一个选项。
(我删除了path
上的匹配,将代码简化为最小的示例。)
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 -> ()
答案 5 :(得分:1)
OCaml电池库集合提供了两个可用于最终的功能
http://ocaml-batteries-team.github.io/batteries-included/hdoc/BatPervasives.html
val finally : (unit -> unit) -> ('a -> 'b) -> 'a -> 'b
finally fend f x
调用f x
,然后调用fend()
即使f x
提出异常。
val with_dispose : dispose:('a -> unit) -> ('a -> 'b) -> 'a -> 'b
with_dispose dispose f x
在f
上调用x
,在dispose x
终止时调用f
(使用返回值或例外)。
答案 6 :(得分:1)
如果函数和finally块都引发异常,我更喜欢看到初始异常,所以我使用这样的东西:
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 *)