模仿尝试与 - 终于在OCaml中(Emulating try-with-finally in OC

2019-07-30 04:50发布

OCaml中的try .. with不提供finally像Java条款。 副作用尤其是在处理时,这将是有益的,但是,。 例如,我想打开一个文件,通过打开文件的功能,并关闭它。 如果函数引发例外,我必须抓住它才能有机会关闭文件。 这得到越来越复杂,当多个文件被打开,开放本身可能也失败。 是否有一个既定的编程模式来处理这个?

下面是说明该问题的简单功能。 函数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

Answer 1:

是否有一个既定的编程模式来处理这个?

是的,从分离异常处理资源清理的包装功能。 我要做的就是用一个通用的包装, 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的,即使它本身出现故障时可以被称为一次亚龙Minski的 ,我认为这还是一个有点更清晰:

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_功能是,我发现它对于高阶编程更方便; 特别是,通过定义应用程序操作者点菜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 -> (*...*)



Answer 2:

从书中OCaml中的Unix系统编程由Xavier乐华和Didier雷米在章通则

“有没有内置敲定构建尝试...敲定在OCaml的语言,但它可以很容易地定义为:”

let try_finalize f x finally y =
  let res = try f x with exn -> finally y; raise exn in
  finally y;
  res


Answer 3:

它不是内置在OCaml中,据我知道,但你可以写一个库,可以让这种编码模式。 一个例子库,如果你能抓住我 ,这是错误的单子编码。 这里是添加教程finally构造一个使用元编程。 可能有其他的方法为好。



Answer 4:

这是一个选项。 (我已删除所述匹配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 -> ()


Answer 5:

OCaml的电池藏书提供了可用于最后两个功能

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

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

finally fend fx电话fx ,然后fend()即使fx引发异常。

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

with_dispose dispose fx调用fx ,呼叫dispose xf终止(或者与一个返回值或异常)。



Answer 6:

如果这两个功能和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 *)


文章来源: Emulating try-with-finally in OCaml