Ocaml 的 sprintf 不适用于 %a 格式说明符

Ocaml's sprintf doesn't work with %a format specifier

sprintf 似乎不允许在其格式说明符中使用 %a。这是正确的吗?如果是这样,为什么会这样,有解决方法吗?

示例。我有一个又长又复杂的数据类型:

type t = Foo | Baz

我有一个适合这种类型的漂亮打印机,它将其值的字符串表示形式发送到任意 out_channel:

let pp_t oc = function
  | Foo -> fprintf oc "Foo"
  | Baz -> fprintf oc "Baz"

如果我再写

let _ = printf "%a=%d" pp_t Foo 2

let _ = eprintf "%a=%d" pp_t Foo 2

然后一切正常——消息 "Foo=2" 最终出现在我的标准输出或我的标准错误中,正如预期的那样。但是如果我写

let s = sprintf "%a=%d" pp_t Foo 2

然后我得到一个编译错误,抱怨参数 pp_t 的类型为 out_channel -> t -> unit 但表达式应为 unit -> 'a -> string 类型。

是否根本不可能在 sprintf 的格式说明符中使用 %a

简答

使用 Format 模块进行漂亮的打印,使用 Format.asprintf 函数代替 sprintf 来构造字符串(我通常只在字符串的开头使用 open Format文件)。

长答案

可以将 %a 转换规范与 sprintf 一起使用。但是,所需函数的类型不同于 Printf.fprintfPrintf.printf 所需的类型。

所有pretty-printers(满足%a转换所需的值类型的函数)在其类型中编码输出类型。因此,对于不同的 printf 函数系列,您需要使用不同的 pretty-printing 函数。

对于 Format 模块 pretty-printers,您需要一个 formatter -> 'a -> unit 类型的函数。这是 top-level 和调试器也需要的类型,也是最常见的 pretty-printing 函数类型(Core 等库以及许多其他库提供 pretty-printers 满足这个签名)。 Format 模块还提供了 asprintf 函数,与 sprintf 函数不同,它可以很好地处理 %a 转换(即,它具有相同的 formatter -> 'a -> unit类型)。

Printf.printf 的情况下,pretty-printer 的类型应该是 out_channel -> 'a -> unit。这种类型的通用性要差得多,因此比 Format 的类型更不受欢迎。很少有图书馆提供。

最后,Printf.sprintf 函数需要 unit -> 'a -> string 类型的打印机。通常,获得如此漂亮的打印机的唯一方法是手动编写(有时使用 Printf.asprintf)。在你的情况下它将是:

let pp_t () = function
  | Foo -> "Foo"
  | Baz -> "Baz"

为了避免出现在格式化函数中的特定数据类型,您应该始终使用 Format 模块并为具有签名 val pp_t : Format.formatter -> t -> unit 的类型设计漂亮的打印机;这也是顶层要求为您的数据类型安装带有 #install_printer 的自定义打印机的签名。

配备了这样的功能你可以简单地使用Format.asprintf"%a"