Compiler errors OCaml的行产生了神秘的错误

Compiler errors OCaml的行产生了神秘的错误,compiler-errors,ocaml,Compiler Errors,Ocaml,当我执行代码时 let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;; 我希望看到输出abc3,但得到的是 文件“f.ml”,第1行,字符46-47: 错误:此函数具有类型(单位、输出通道、单位)格式->单位 它适用于太多的论点;也许你忘了一个“;”。 有趣的是,如果我将2+2更改为2,它就会运行 为什么代码会按原样产生错误,但在删除+2后不会产生错误?这是OCaml针对printf的特殊键入技巧和值多态性的组合 您可能知道,Prin

当我执行代码时

let (a,p) = (2+2, Printf.printf) in p "abc"; p "%d" 3 ;;
我希望看到输出
abc3
,但得到的是

文件“f.ml”,第1行,字符46-47:
错误:此函数具有类型(单位、输出通道、单位)格式->单位
它适用于太多的论点;也许你忘了一个“;”。
有趣的是,如果我将
2+2
更改为
2
,它就会运行


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

这是OCaml针对
printf
的特殊键入技巧和值多态性的组合

您可能知道,
Printf.Printf
不接受
string
,而是数据类型
format
。OCaml类型检查器有一个特殊的规则,用于键入
printf
的字符串文本:如果键入为
格式
,如果上下文请求:

# "%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>
这里,
p
具有多态类型
('a,out\u channel,unit)格式->'a
<代码>'a可以实例化为多个类型,因此
p“abc”;p“%d”3
是可类型化的:多态类型可以在第一次使用
p
时实例化为
(unit,out\u channel,unit)格式->unit
,第二次使用
p
时实例化为
(int->unit,out\u channel,unit)格式->int->unit

一旦将常量
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
的类型变为
(单位,输出通道,单位)格式->单位
。它只能使用1个参数,因此第二次使用
p
时键入2个参数失败

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

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

我对答案也很感兴趣,这似乎很奇怪。
let a = 2 + 2 in
let p = Printf.printf in
p "abc"; p "%d" 3