Exception handling 在OCaml中模拟try with finally

Exception handling 在OCaml中模拟try with finally,exception-handling,ocaml,Exception Handling,Ocaml,OCaml的try。。with不提供类似Java的finally子句。不过,这会很有用,尤其是在处理副作用时。例如,我喜欢打开一个文件,将打开的文件传递给一个函数,然后关闭它。如果函数引发异常,我必须捕获它,以便有机会关闭文件。当打开多个文件时,这会变得越来越复杂,而且打开本身也可能失败。是否有一个既定的编程模式来处理这个问题 下面是一个简单的函数来说明这个问题。如果提供了路径,则功能f应用于属于文件的频道,否则功能stdin。因为没有finally子句,close\u在io中出现两次 let

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

下面是一个简单的函数来说明这个问题。如果提供了
路径
,则功能
f
应用于属于文件的频道,否则功能
stdin
。因为没有finally子句,
close\u在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

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

这里有一个选项。 (我已经删除了
路径上的匹配项
,将代码归结为一个最小的示例。)

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

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

这是一个简单的包装器,无法正确解释
protect
中引发的异常;一个经过充分检查的包装器可以确保即使它本身失败也只调用一次
protect
,或者我认为更清楚一点的是:

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)
我用函数切换特定
的参数的原因是我发现它更便于高阶编程;特别是,通过定义应用程序操作符a 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)
它可以与调用一起使用,如Xavier Leroy和Didier Rémy在本章中的书中的“400x300”$fun width height->(*…*)

OCaml语言中没有内置的finalize构造try…finalize,但它可以轻松定义:


OCaml电池库集合提供了两个功能,可用于

finally fend fx
调用
fx
,然后调用
fend()
,即使
fx
引发了异常

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

with_dispose dispose f x
x
上调用
f
,在
f
终止时调用
dispose x
(返回值或异常)。

如果函数和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 *)

从OCAML4.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

谢谢你的建议。我希望在核心语言中找到一个智能的解决方案,而不是依赖于Camlp5。这里的想法是存储异常,以避免重复进入最后一个块的代码。另一种设计是捕获本地函数中的最后一个块,并从两个地方调用它。非常好!顺便说一句:我假设
$
被定义为
let($)fx=fx
,但这会使它左关联,而在Haskell中它是右关联的。因此,
print_int$(+)3$4
在OCaml中不起作用。对于右关联应用运算符,可以定义
let(@)fx=fx
。如果
protect
引发异常,是否执行两次?因为捕捉到异常并再次执行
protect
。另请参见。@ChristianIndig:是的,我的原始代码不处理双重异常。请参阅我的编辑,了解我认为在Yaron的基础上改进的“功能性”版本。NB extlib有Std.finally(它为清理失败时引发的异常做了另一个选择).
Fun.protect
可用于定义一个类似于Go中的函数的
defer
函数:
let defer f=Fun.protect~最后:f
现在您可以使用:
let io=open_in path in defer in defer(Fun()->close_in io)@@f io
这样,任何关闭操作都是在打开之后定义的,函数的其余部分就不必担心它了。
val finally : (unit -> unit) -> ('a -> 'b) -> 'a -> 'b
val with_dispose : dispose:('a -> unit) -> ('a -> 'b) -> 'a -> 'b
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 *)
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