如何解决一元文件 I/O
有许多关于如何读取和写入文件的示例,但许多帖子似乎已过时、are too complicated 或不“安全”(1、2) (他们抛出/引发异常)。来自 Rust,我想用像 result
这样的 monadic 来明确处理所有错误。
下面是一个“更安全”的尝试,因为打开和读/写不会抛出/提高。但不确定关闭是否会失败。有没有更简洁、可能更安全的方法来做到这一点?
(* opam install core batteries *)
open Stdio
open Batteries
open BatResult.Infix
let read_safe (file_path: string): (string,exn) BatPervasives.result =
(try let chan = In_channel.create file_path in Ok(chan)
with (e: exn) -> Error(e))
>>= fun chan ->
let res_strings =
try
let b = In_channel.input_lines chan in
Ok(b)
with (e: exn) -> Error(e) in
In_channel.close chan;
BatResult.map (fun strings -> String.concat "\n" strings) res_strings
let write_safe (file_path: string) (text: string) : (unit,exn) BatPervasives.result =
(try
(let chan = Out_channel.create file_path in Ok(chan))
with (e: exn) -> Error(e))
>>= fun chan ->
let res =
(try let b = Out_channel.output_string chan text in Ok(b)
with (e: exn) -> Error(e)) in
Out_channel.close chan;
res
let () =
let out =
read_safe "test-in.txt"
>>= fun str -> write_safe "test-out.txt" str in
BatResult.iter_error (fun e -> print_endline (Base.Exn.to_string e)) out
解决方法
Stdio
库是 Janestreet 工业强度标准库的一部分,已经提供了这样的函数,这些函数当然是安全的,例如 In_channel.read_all 读取文件的内容以一个字符串和相应的 Out_channel.write_all 将其写入文件,因此我们可以实现一个 cp
实用程序,
(* file cp.ml *)
(* file cp.ml *)
open Base
open Stdio
let () = match Sys.get_argv () with
| [|_cp; src; dst |] ->
Out_channel.write_all dst
~data:(In_channel.read_all src)
| _ -> invalid_arg "Usage: cp src dst"
要构建和运行代码,请将其放入 cp.ml
文件(最好是在新目录中),然后运行
dune init exe cp --libs=base,stdio
此命令将使用 dune 引导您的项目。然后你可以用
运行你的程序dune exec ./cp.exe cp.ml cp.copy.ml
这里是 OCaml Documentation Hub 的链接,可以让您更轻松地在 OCaml 中找到有趣的库。
另外,如果你想把一个引发异常的函数变成一个返回错误的函数,你可以使用Result.try_with,例如,
let safe_read file = Result.try_with @@ fun () ->
In_channel.read_all file
,
您可以在 OCaml 中读取和写入文件,而无需其他标准库。您需要的一切都已内置于 OCaml 附带的 Stdlib 中。
以下是读取文件同时确保文件描述符在出现异常时安全关闭的示例:https://stackoverflow.com/a/67607879/20371。从那里您可以编写一个类似的函数来使用相应的函数 open_out
、out_channel_length
和 output
来编写文件。
这些读写文件内容为 OCaml 的 bytes
类型,即可变字节串。但是,它们可能会引发异常。这可以。在 OCaml 中,异常便宜且易于处理。然而,有时人们不喜欢它们,无论出于何种原因。因此,现在使用 _exn
为抛出异常的函数添加后缀是一种惯例。因此,假设您将上述两个函数定义为:
val get_contents_exn : string -> bytes
val set_contents_exn : string -> bytes -> unit
现在您(或任何人)可以轻松地将它们包装起来并返回一个 result
值,就像 Rust 一样。但是,由于我们在 OCaml 中有多态变体,我们利用它来组合可以返回 result
值的函数,如下所述:https://keleshev.com/composable-error-handling-in-ocaml
所以你可以像这样包装它们:
let get_contents filename =
try Ok (get_contents_exn filename) with exn -> Error (`Exn exn)
let set_contents filename contents =
try Ok (set_contents_exn filename contents) with exn -> Error (`Exn exn)
现在这些有类型:
val get_contents : string -> (bytes,[> `Exn of exn]) result
val set_contents : string -> bytes -> (unit,[> `Exn of exn]) result
它们可以相互组合在一起,并与其他返回 result
值的函数组合在一起,并带有多态变体错误通道。
我在这里想说明的一点是,为您的用户提供这两种方式,以便他们可以选择对他们有意义的任何方式——例外或result
。
这是基于 @ivg 答案的完整安全解决方案(非常好),仅使用 Base
库。
(请赞成他的回答,而不是这个。)
open Base
open Base.Result
open Stdio
let read_safe (file_path: string) =
Result.try_with @@ fun () ->
In_channel.read_all file_path
let write_safe (file_path: string) (text: string) =
Result.try_with @@ fun () ->
Out_channel.write_all ~data:text file_path
let () =
let out =
read_safe "test-in.txt"
>>= fun str ->
write_safe "test-out.txt" str in
iter_error out ~f:(fun e -> print_endline (Base.Exn.to_string e))
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。