OCaml标记的参数顺序为高阶函数

时间:2015-11-03 21:48:12

标签: compiler-errors arguments ocaml

在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

我想知道为什么会这样。看来这里没有任何歧义,编译器可以正确推断出一切吗?

2 个答案:

答案 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 -> unitfoo在获取第一个参数后打印"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 -> 'csecond:'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的功能,以便在某些情况下可以省略参数名称。因此,这将是对语言的不兼容的改变。