10

OCamltry .. with不提供finally像 Java 这样的子句。不过,它会很有用,尤其是在处理副作用时。例如,我喜欢打开一个文件,将打开的文件传递给一个函数,然后关闭它。如果函数引发异常,我必须捕获它才能有机会关闭文件。当打开多个文件并且打开​​本身也可能失败时,这会变得越来越复杂。是否有既定的编程模式来处理这个问题?

下面是一个说明问题的简单函数。如果提供了 a ,则函数f应用于属于文件的通道,否则。因为没有 finally 子句,所以出现了两次。pathstdinclose_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
4

7 回答 7

8

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

是的,将资源清理与异常处理分离的包装函数。我所做的是使用通用包装器unwind(我更喜欢使用的 LISPism):

let unwind ~(protect:'a -> unit) f x =
  try let y = f x in protect x; y
  with e -> protect x; raise e

这是一个简单的包装器,它不能正确解释 ; 中引发的异常protectprotect一个经过全面检查的包装器,即使它本身失败也确保只调用一次可能是Yaron 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_是我觉得高阶编程更方便;特别是,通过定义一个应用程序运算符à 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 -> (*...*)

于 2012-06-30T22:31:57.197 回答
4

从 OCaml 4.08 开始,Fun.protect标准库中有一个提供此功能的函数。

使用它,您的示例将如下所示:

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
于 2021-05-06T22:24:46.340 回答
3

摘自Xavier Leroy 和 Didier Rémy所著的 OCaml 中的 Unix 系统编程一书,在通用性一章中

“OCaml 语言中没有内置的 finalize 构造 try ...finalize,但它可以很容易地定义:”

let try_finalize f x finally y =
  let res = try f x with exn -> finally y; raise exn in
  finally y;
  res
于 2013-04-06T21:45:16.347 回答
2

据我所知,它不是 OCaml 内置的,但您可以编写一个库来对此类模式进行编码。一个示例库是Catch me if you can,它是错误的一元编码。finally是使用元编程添加构造的教程。可能还有其他方法。

于 2012-06-30T19:31:43.023 回答
1

OCaml Batteries 库集合提供了两个最终可用于

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调用fon xdispose xf终止时调用(返回值或异常)。

于 2013-11-05T12:23:16.267 回答
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 *)
于 2013-11-05T15:44:12.220 回答
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 -> ()
于 2012-06-30T20:15:51.630 回答