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
当我执行代码时
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