如何在使用用户提供的格式字符串的OCaml printf之后触发操作

时间:2011-07-11 15:02:07

标签: logging ocaml printf

我有一个打印字符串并退出的简单函数:

let fatal s = 
  print_string "Log: ";
  print_endline s;
  exit 1

我可以使用printf在没有exit 1的情况下执行类似操作:

let log fmt = 
  printf ("Log: " ^^ fmt)

此日志函数采用格式字符串并返回一个函数,该函数获取该格式字符串所需的参数并在前面打印“Log:”。 (当然,对于我的实际应用,我的前缀不是那么简单。)

将这两者结合起来并不容易。第一次尝试:

let fatalf fmt =
   Printf.printf ("Log: " ^^ fmt) 
   ???
   exit 1

问题是我必须返回我的printf表达式的结果,以便可以将剩余的参数应用于它。一旦我返回了这个值,我就没有流控制来运行exit

printf格式化程序%t看起来很有用,因为它需要一个函数并运行它:

printf ("Log: " ^^ fmt ^^ "%!%t") ... (fun _ -> exit 1)

这似乎不起作用,因为%t必须是最后的,所以它在写入日志消息后运行,但这意味着退出函数必须在用户指定的参数之后,并且因为没有办法要知道有多少参数可以进行干预,在给出干预参数时,不能生成一个完全应用printf的闭包。

我记得有一些支持命名的printf参数,但这是因为它是错误的。有没有办法模仿,或者实现所谓的“在任​​意printf之后退出”行为?

2 个答案:

答案 0 :(得分:8)

您正在寻找Printf.kprintf

let fatalf fmt =
  Printf.kprintf (fun str ->
    Printf.eprintf "Fatal error: %s !\n%!" str;
    exit 1) fmt

kprintf继续使用string -> 'a类型,并将其应用于sprintf的结果,并使用提供的格式。延续的结果是整个调用的结果,正如预期的那样。

答案 1 :(得分:1)

printf是OCaml的棘手部分,因为它受编译魔术的支持。如果源包含字符串常量且其类型环境需要printf格式,则编译器会神奇地将字符串常量转换为格式。这很光滑,但是一旦你超越了最简单的用途,你有时需要自己做魔术。特别要注意的是,它必须是一个字符串常量,以便编译器能够强制执行强类型。

您的第二个示例未显示s参数,但我认为您只是将其关闭。如果您只想使用也作为参数传递的格式打印一个参数,您可以执行以下操作:

let fatalfs f s =
    printf ("Log:" ^^ f) s;
    exit 1

^^运算符将两种格式连接成一种格式。

这是一个有这个功能的会话:

$ rlwrap ocaml312
    Objective Caml version 3.12.0

# let fatalfs f s = Printf.printf ("Log: " ^^ f) s; exit 1;;
val fatalfs :
  ('a -> 'b, out_channel, unit, unit, unit, unit) format6 -> 'a -> 'c = <fun>
# fatalfs "Here is the value: [%s]\n" "value";;
Log: Here is the value: [value]
$ echo $?
1
$

请注意,fatalfs实际上是参数s类型的多态。只要格式字符串的类型和第二个参数匹配,它就可以工作。这真是令人印象深刻。