首页 > 解决方案 > OCaml 中的 Printf 参数

问题描述

在 OCaml 中,我在传递应该适用于Printf.printf. 这可能是因为我不完全了解该功能,但我无法确定什么不起作用。

首先,我定义了一个函数(用于记录):

utop # let log verbosity level str =
  if level <= verbosity then (
    Printf.printf "\nLevel %i: " level;
    Printf.printf str);;
val log : int -> int -> (unit, out_channel, unit) format -> unit = <fun>

一切似乎都很好,但后来我得到了这个:

utop # log 0 0 "%i" 0;;
Error: This function has type
         int -> int -> (unit, out_channel, unit) format -> unit
       It is applied to too many arguments; maybe you forgot a `;'.

尽管以下工作:

utop # Printf.printf;;
- : ('a, out_channel, unit) format -> 'a = <fun>
utop # Printf.printf "%i" 0;;
0- : unit = ()

那么,我怎样才能定义一个函数来做log打算做的事情呢?

编辑:确实,log 0 0 "%i" 0;;看起来参数太多(4而不是3),但也是如此Printf.printf "%i" 0;;(2而不是1),并且它有效。通过部分应用,这给出了:

utop # Printf.printf "%i";;
- : int -> unit = <fun>

utop # log 0 0 "%i";;
Error: This expression has type (unit, unit) CamlinternalFormatBasics.precision
       but an expression was expected of type
         (unit, int -> 'a) CamlinternalFormatBasics.precision
       Type unit is not compatible with type int -> 'a 

标签: printfocaml

解决方案


printf-like 函数是可变参数的,因为它们接受可变数量的参数。它不是真正特定于printf家族的,您可以在 OCaml 中定义自己的可变参数函数,类型系统完全支持这一点。printf 的唯一魔力在于编译器将字符串文字翻译为例如"foo %d类型的值format

现在,让我们看看printf函数的类型,

('a, out_channel, unit) format -> 'a

请注意,它返回'a的是类型变量。因为'a可以是任何东西,它也可以是一个函数。是格式字符串的('a, out_channel, unit) format类型,它定义了由该格式字符串生成的函数的类型。但重要的是要理解,尽管它"foo %d"看起来像一个字符串,但实际上它是 type 的一个特殊内置值_ format,它有一个看起来像字符串的文字(尽管并非所有有效的字符串都是该_ format类型的有效文字.

只是为了证明 的第一个参数printf不是字符串,让我们尝试以下操作,

# Printf.printf ("foo " ^ "%d");;
Line 1, characters 14-29:
1 | Printf.printf ("foo " ^ "%d");;
                  ^^^^^^^^^^^^^^^
Error: This expression has type string but an expression was expected of type
         ('a, out_channel, unit) format

现在,当我们知道 printf 不是一个典型的函数时,让我们自己定义一个类似 printf 的函数。为此,我们需要使用kprintf-family 函数,例如,

# #show Printf.ksprintf;;
val ksprintf : (string -> 'd) -> ('a, unit, string, 'd) format4 -> 'a

该函数采用接收我们可以记录的结果字符串的函数,例如,

# let log fmt = Printf.ksprintf (fun s -> print_endline ("log> "^s)) fmt;; 
val log : ('a, unit, string, unit) format4 -> 'a = <fun>
# log "foo";;
log> foo
- : unit = ()

这个生成的函数看起来更像sprintf,即,它将与使用字符串作为输出设备的漂亮打印函数很好地配合(这是一个不同的主题)。Printf.kfprintf您可能会发现使用or 或更好地使用Format.kasprintfor来定义日志记录功能会更容易Format.kfprintf。后两个函数有以下类型,

val kasprintf : (string -> 'a) -> ('b, formatter, unit, 'a) format4 -> 'b
val kfprintf : (formatter -> 'a) -> formatter ->
  ('b, formatter, unit, 'a) format4 -> 'b

但是格式的类型与漂亮的打印机(通常命名为)所接受的formatter类型(输出设备的抽象)一起使用。pp因此使用该Format模块定义的日志函数将与现有库更好地配合。

因此,使用Format.kasprintf我们可以将您的日志函数定义为,

# let log verbosity level =
  Format.kasprintf (fun msg -> 
      if level <= verbosity then 
        Format.printf "Level %d: %s@\n%!" level msg);;

val log : int -> int -> ('a, Format.formatter, unit, unit) format4 -> 'a = <fun>

这是它的使用方法,

# log 0 0 "Hello, %s, %d times" "world" 3;;
Level 0: Hello, world, 3 times
- : unit = ()

推荐阅读