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