Python 的 "with" 语句(自动释放资源)的 OCaml 对应项是什么

What is the OCaml counterpart to Python's "with"-statement (automatic release of resources)

Python 的 "with" 语句对应的 OCaml 是什么?

with open('test.txt', 'r') as f:
    # Do stuff with f
# At this point, f will always be closed, even in case of exceptions

即:OCaml 中的首选方式是什么 以安全地确保某个资源(打开的文件、数据库连接、HTTP 连接等)始终在以下位置被释放某个时间点?在这里等待垃圾收集器是没有选择的,异常不应该阻止资源被释放。

当然,在 OCaml 中,您始终可以使用 try-finally 并关闭文件 "by hand",就像在 Python 中一样。但是,这种代码很容易出错。这就是 Python 引入 "with" 语句的原因。使此类代码更易于阅读且不易出错的 OCaml 习语是什么?

请注意,这个问题与 Emulating try-with-finally in OCaml, as this is one step further: I don't just want to emulate try-finally in OCaml! (where Lwt's [%finally ...] 做得很好的问题有很大的不同。)地方 - 正如在 Python.

中所做的那样

另请注意,这个问题不是关于实现细节,而是关于成语:所有可能的设计和解决方案中哪些获得了一些关注在 OCaml 社区中并被普遍接受?

python 中的

"with" 使用特殊对象来管理资源并具有在 "with" 完成时调用的清理函数。 Ocaml 没有这样的东西。但是你可以实现它们。

类似于:

let with_ (constructor, destructor) fn =
   let obj = constructor () in
   try
       let res = fn obj in
       destructor obj;
       res
   with exn ->
       destructor obj;
       raise exn

或者使用具有 destroy 方法的 ocaml 对象。

归结为您将 finally 子句隐藏在析构函数或 destroy 方法中,这样您就不必手动编写它。

在"No macros")

下有一些合理的答案here (emulating try with finally in ocaml), although the absence of macros makes it somewhat more cumbersome than would otherwise be the case (see eg this complaint

JaneStreet 的 core_kernel 标准库替换以 In_channel.with_file 的形式提供了您所需要的。因此,如果您有机会使用 core_kernel,请参阅此处的用法示例:https://dev.realworldocaml.org/imperative-programming.html#file-io

另一个简单的实现。

让我们首先定义一个表示结果或异常的类型:

type 'a okko = Ok of 'a | Ko of exn

然后,定义capture_errors:

let capture_errors fn = try Ok (fn ()) with e -> (Ko e);;

你可以让他们实施 unwind_protect:

let unwind_protect wind unwind =
  let result = (capture_errors wind) in
  begin
    unwind ();
    match result with
    | Ok (result) -> result
    | Ko (error) -> raise error
  end;;

上面一直执行unwind一次。

然后您可以定义通用 with_ 函数:

let with_ enter leave body =
    let e = enter() in
    unwind_protect
      (fun () -> (body e))
      (fun () -> (leave e)) ;;

例如,with_open_file 可能定义为:

let with_open_file opener closer file fn =
  with_
    (fun () -> (opener file))
    (fun (chan) -> (closer chan))
    fn

在常见情况下,您可以柯里化 open_inclose_in

let with_input_file = with_open_file open_in close_in;;

例如:

with_input "/etc/passwd" input_line

现在可以将 Fun.protect 视为(有效的)成语,因为它在标准库中。例如,

let get_contents file =
  let ch = open_in file in
  Fun.protect ~finally:(fun () -> close_in ch) begin fun () ->
    let len = in_channel_length ch in
    let bytes = Bytes.create len in
    ignore (input ch bytes 0 len);
    bytes
  end

如今,甚至还有 let-operators 正在慢慢找到更频繁使用的方​​式,例如https://github.com/ocaml/ocaml/pull/9887

所以你可以定义一个 let-op 来使用一个文件,比如:

let ( let& ) ch fn =
  Fun.protect ~finally:(fun () -> close_in ch) begin fun () ->
    fn ch
  end

并像这样使用它:

let get_contents file =
  let& ch = open_in file in
  let len = in_channel_length ch in
  let bytes = Bytes.create len in
  ignore (input ch bytes 0 len);
  bytes

let& 运算符确保 in_channel 在当前作用域 (get_contents) 的末尾关闭。