在OCaml中模拟try-with-finally

Chr*_*dig 7 ocaml exception-handling

OCaml try .. with不提供finally像Java这样的子句.但是,这将是有用的,特别是在处理副作用时.例如,我想打开一个文件,将打开的文件传递给一个函数,然后关闭它.如果函数引发异常,我必须抓住它以便有机会关闭文件.当打开多个文件并且打开​​自身也可能失败时,这变得越来越复杂.是否有一个既定的编程模式来处理这个问题?

下面是一个说明问题的简单函数.f如果path提供了a,则将函数应用于属于文件的通道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
Run Code Online (Sandbox Code Playgroud)

小智 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
Run Code Online (Sandbox Code Playgroud)

这是一个简单的包装器,它没有正确解释引发的异常protect; 一个完全检查的包装器,确保protect只调用一次,即使它自身失败也可能是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
Run Code Online (Sandbox Code Playgroud)

然后,我根据需要定义特定实例,例如:

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)
Run Code Online (Sandbox Code Playgroud)

我为特定with_功能切换参数的原因是我发现它对于高阶编程更方便; 特别是,通过定义应用程序运算符la Haskell,我可以写:

let () = with_output_file "foo.txt" $ fun otch ->
  output_string otch "hello, world";
  (* ... *)
Run Code Online (Sandbox Code Playgroud)

语法不是很重.有关更为复杂的示例,请考虑以下事项:

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)
Run Code Online (Sandbox Code Playgroud)

这可以像一个电话一样使用with_open_graph " 400x300" $ fun width height -> (*...*).

  • 非常好!稍微搁置一下:我假设`$`被定义为`let($)fx = fx`但是这会使它保持关联,而在Haskell中它是正确的关联.因此`print_int $(+)3 $ 4`在OCaml中不起作用.对于右关联应用运算符,可以定义`let(@@)fx = fx`. (2认同)

Sco*_*der 6

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

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

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
Run Code Online (Sandbox Code Playgroud)