当我执行代码时
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
?
答案 0 :(得分:6)
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个参数,因此第二次使用带有2个参数的p
的类型失败。
避免这种情况的一种简单方法是将您的定义分成两部分:
let a = 2 + 2 in
let p = Printf.printf in
p "abc"; p "%d" 3