OCaml线产生神秘的错误

fee*_*sum 6 ocaml compiler-errors

当我执行代码

let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;;
Run Code Online (Sandbox Code Playgroud)

我希望看到输出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 `;'.
Run Code Online (Sandbox Code Playgroud)

有趣的是,如果我换2+22,它会运行.

为什么代码会产生错误,但不会+2删除?

cam*_*ter 6

OCaml的特殊输入技巧printf和价值多态的组合.

您可能知道,Printf.printf不采用string数据类型format.OCaml类型检查器有一个特殊的规则,用于键入字符串文字printf:如果键入的内容就像format上下文请求一样:

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

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

# fun x -> x;;
- : 'a -> 'a = <fun>
# (fun x -> x) (fun x -> x)
- : '_a -> '_a = <fun>
Run Code Online (Sandbox Code Playgroud)

在上面,(fun x -> x) (fun x -> x)没有多态类型,而身份函数fun x -> x有.这是由于表达形式(fun x -> x) (fun x -> x):它是"膨胀的".奇怪的类型变量'_a是单态类型变量:它只能被实例化为某种类型.另一方面,'a对于每次使用vlaue,可以将多态变量实例化为不同的类型.

我们回到你的代码:

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

这里,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.

一旦你改变常数22+2,这是广阔的,整个表达式变得膨胀也和打字的变化:

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

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

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

let a = 2 + 2 in
let p = Printf.printf in
p "abc"; p "%d" 3
Run Code Online (Sandbox Code Playgroud)