OCaml行产生神秘错误

Line of OCaml produces mysterious error

当我执行代码时

let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;;

我希望看到输出 abc3,但得到的却是

File "f.ml", line 1, characters 46-47:
Error: This function has type (unit, out_channel, unit) format -> unit
       It is applied to too many arguments; maybe you forgot a `;'.

有趣的是,如果我将 2+2 更改为 2,它就会运行。

为什么代码会按原样产生错误,但在删除 +2 后却不会?

OCaml 的特殊打字技巧 printf 和值多态性的组合。

您可能知道,Printf.printf 不采用 string 而是数据类型 format。 OCaml 类型检查器对于 printf 的字符串文字类型有一个特殊规则:如果它被类型化为 format 如果上下文请求:

# "%d";;
- : string = "%d"
# ("%d" : _format);;
- : (int -> 'a, 'b, 'a) format = ...

OCaml 类型系统还有一个技巧叫做值多态性(更准确地说,它是宽松的值多态性)。其目的是正确键入具有副作用的表达式。我不解释它的细节,但它限制了多态性:一些叫做 "expansive" 的表达式不能有多态类型:

# fun x -> x;;
- : 'a -> 'a = <fun>
# (fun x -> x) (fun x -> x)
- : '_a -> '_a = <fun>

上面(fun x -> x) (fun x -> x)没有多态类型,而恒等函数fun x -> x有。这是由于 (fun x -> x) (fun x -> x) 的表达式的形状:它是 "expansive"。奇怪的类型变量 '_a 是单态类型变量:它只能被实例化一次。另一方面,对于 vlaue.

的每次使用,可以将像 'a 这样的多态变量实例化为不同的类型。

让我们回到您的代码:

# let (a, p) = (2, Printf.printf);;
val a : int
val p : ('a, out_channel, unit) format -> 'a

这里,p 有一个多态类型 ('a, out_channel, unit) format -> 'a'a 可以实例化为多个类型,因此 p "abc"; p "%d" 3 是可类型化的:多态类型可以实例化为 (unit, out_channel, unit) format -> unit 以供首次使用 p(int -> unit, out_channel, unit) format -> int -> unit 第二次使用 p.

一旦将常量 2 更改为可扩展的 2+2,整个表达式也将变得可扩展,并且类型会发生变化:

# let (a, p) = (2+2, Printf.printf);;
val a : int
val p : ('_a, out_channel, unit) format -> '_a

这里,p不再是多态变量'a,而是单态变量'_a。这个单态变量在第一次使用 p 时统一(实例化)为 unit,结果 p 的类型变为 (unit, out_channel, unit) format -> unit。它只能接受 1 个参数,因此第二次使用带有 2 个参数的 p 的输入失败。

避免这种情况的一种简单方法是将您的定义分成两部分:

let a = 2 + 2 in
let p = Printf.printf in
p "abc"; p "%d" 3