在 Ocaml 中引发有意义的异常

Raising meaningful exceptions in Ocaml

我想在 OCaml 中引发定义的异常时打印一条有意义的消息:

type t = A | B of int
exception Wrong of t

假设我有一个函数

let string_of_t = function
  | A -> "A"
  | B n -> ("B" ^ (string_of_int n))

那我想要一个功能

val print_exception : ( 'a -> string ) -> a' exn -> string

这样我就可以定义

let my_raise e =
  print_endline ("Error: I got the unexpected value" ^ (print_exception string_of_t e));
  raise e    [???]

有没有这个功能print_exception?

问题不是很恰当(例如,没有 a' exn 类型),但我希望我的意图是可以理解的。我已经看到可以使用 [@@deriving sexp] 但这看起来像是语言之外的一些魔法,并且可能在语言之内有更简单的东西。

有两种方法。第一个是使用 Printexc,第二个是匹配所有你想打印的异常并相应地打印,比如:

exception Zero of int
exception B of string

let pp ppf = function
  | Zero d -> Format.fprintf ppf "Zero of %d" d
  | B s -> Format.fprintf ppf "B of %s" s
  | Not_found -> Format.fprintf ppf "Not found"
  | _ -> Format.fprintf ppf "Your exception is in another castle"

let f n d = if d = 0 then raise (Zero d) else n / d

let () =
  let n, d = (10, 0) in
  try Format.printf "%d/%d is %d@." n d (f n d)
  with e ->
    Format.eprintf "%a@." pp e;
    Format.eprintf "%s@." (Printexc.to_string e)

会给予

❯ ./exc
Zero of 0
Exc.Zero(0)

将两者结合起来似乎是能够自定义某些显示并让所有其他显示成为默认显示的最佳解决方案:

let pp ppf = function
  | Zero d -> Format.fprintf ppf "Zero of %d" d
  | e -> Format.fprintf ppf "%s" (Printexc.to_string e)

在 OCaml 中,我们(还)没有模块化隐式,因此除非您使用 [@@derive ...],否则您需要使用两种解决方案之一。

作为side-note,可以在模式匹配中捕获异常:

let () =
  let n, d = (10, 0) in
  match f n d with
  | r -> Format.printf "%d/%d is %d@." n d r
  | exception e ->
      Format.eprintf "%a@." pp e;
      Format.eprintf "%s@." (Printexc.to_string e)

它在语义上与我之前写的一样,但它对调用堆栈更好(如果我没记错的话)

[编辑] 看来我忘记了第三个解决方案,请参阅@octachron 的回答

如果您想在引发之前记录一条有意义的消息,在我看来,将日志记录和异常引发结合在一个函数中可能更简单,而不是在事实。例如下面的函数

let log_and_raise exn fmt =
  Format.kfprintf
  (fun ppf -> Format.pp_print_newline ppf (); raise exn)
  Format.err_formatter fmt 

可以这样用

exception A of int
let test n = log_and_raise (A n) "Raising the exception (A %d)" n

并在 stderr 上打印错误消息后将引发异常 A n