当我执行代码时
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 类型系统还有一个技巧叫做值多态性(更准确地说,它是松弛值多态性)。其目的是正确键入带有副作用的表达式。我不解释它的细节,但它限制了多态性:某些称为“扩展”的表达式不能具有多态类型:
# 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)
表达式的形状所致: 它是“膨胀的”。奇怪的类型变量 '_a
是单态类型变量:它只能被实例化为某种类型一次。另一方面,像 'a
这样的多态变量可以为每次使用 vlaue 实例化为不同的类型。让我们回到你的代码:
# 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
.这个单态变量统一(实例化)为 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
关于compiler-errors - OCaml 行产生神秘错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32839002/