在Real World OCaml的this section中,它基本上说:
let apply_to_tuple f (first, second) = f ~first ~second
let apply_to_tuple_2 f (first, second) = f ~second ~first
let divide ~first ~second = first / second
这允许apply_to_tuple divide (3, 4)
有效,但不能apply_to_tuple_2 divide (3, 4)
。后者抛出:
Error: This expression has type first:int -> second:int -> int
but an expression was expected of type second:'a -> first:'b -> 'c
我想知道为什么会这样。看来这里没有任何歧义,编译器可以正确推断出一切吗?
答案 0 :(得分:3)
虽然看起来似乎
first:int -> second:int -> int
和
second:int -> first:int -> int
是相同的,它们实际上不是由于副作用。
考虑以下两个功能:
let foo ~a =
print_endline "a";
fun ~b ->
print_endline "b"
let bar ~b =
print_endline "b";
fun ~a ->
print_endline "a"
foo
的类型为a:'a -> b:'a -> unit
,而bar
的类型为b:'a -> a:'a -> unit
。 foo
在获取第一个参数后打印"a"
,在获取第二个参数后打印"b"
。 bar
在收到第一个参数后打印"b"
,在收到第二个参数后打"a"
。
OCaml确保在提供副作用之前的所有参数时,这些函数中的副作用正好发生。
因此foo
会在给出标有"a"
的参数后打印~a
:
# let foo2 = foo ~a:();;
a
val foo2 : b:'_a -> unit = <fun>
,一旦提供了"b"
和~a
,它就会打印~b
:
# foo2 ~b:();;
b
- : unit = ()
而bar
在给出标有~a
的参数时不会打印任何内容:
# let bar2 = bar ~a:();;
val bar2 : b:'a -> unit = <fun>
因为两个print语句都在~b
参数下面。一旦给出~b
参数 - 以便同时提供~a
和~b
- 它将同时打印"b"
和"a"
:< / p>
# bar2 ~b:();;
b
a
- : unit = ()
维持这样的副作用的正确排序要求OCaml将第一个带标签的参数与第二个带标签的参数区别对待:
当OCaml看到第一个带标签的参数的应用时,它必须应用该函数并在其下面执行任何副作用。
当OCaml看到第二个参数的应用时,它必须构建一个等待第一个参数的新函数,当它接收到它时,将使用第一个和第二个参数应用原始函数。
这意味着类型中参数的顺序很重要,您不能简单地使用second:int -> first:int -> int
值,其中first:int -> second:int -> int
值是预期的。
OCaml可能会尝试在运行时区分第一个参数与其他应用程序的应用程序,因此不需要在类型系统中跟踪这一点,但这会使标记函数的效率低于常规函数
答案 1 :(得分:2)
OCaml允许您调用省略参数名称,只要您提供所有参数即可。在这种情况下,参数按顺序进行。因此,first:'a -> second:'b -> 'c
和second:'b -> first:'a -> 'c
类型不同。
在我看来,你需要放弃没有名字的电话才能获得你想要的灵活性。
# let f ~a ~b = a - b;;
val f : a:int -> b:int -> int = <fun>
# f 4 3;;
- : int = 1
您可以为f
的{{1}}参数指定特定顺序,这样可以进行输入。
apply_to_tuple2
<强>更新强>
以下是关于我声称的更多细节。
首先,# let apply_to_tuple2 (f: first:'a -> second:'b -> 'c) (first, second) =
f ~second ~first;;
val apply_to_tuple2 : (first:'a -> second:'b -> 'c) -> 'a * 'b -> 'c = <fun>
# let divide ~first ~second = first / second;;
val divide : first:int -> second:int -> int = <fun>
# apply_to_tuple2 divide (3, 4);;
- : int = 0
和apply_to_tuple2
的类型:
divide
因此,# let apply_to_tuple_2 f (first, second) = f ~second ~first;;
val apply_to_tuple_2 : (second:'a -> first:'b -> 'c) -> 'b * 'a -> 'c = <fun>
# let divide ~first ~second = first / second;;
val divide : first:int -> second:int -> int = <fun>
f
参数的类型为apply_to_tuple2
。但划分的类型是second:'a -> first:'b -> 'c
。这些类型无法统一,因为命名参数的顺序在OCaml中很重要。
如果您更改了OCaml以使命名参数的顺序无关紧要,则可以使这些类型匹配。但这不是OCaml现在的工作方式。
此外,如果您确实进行了此更改,则还必须删除OCaml的功能,以便在某些情况下可以省略参数名称。因此,这将是对语言的不兼容的改变。