OCaml 用高阶函数标记参数顺序
OCaml labelled arguments order with higher-order functions
在 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
我想知道为什么会这样。似乎这里没有任何歧义,编译器可以正确地推断出所有内容吗?
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
您可以为 apply_to_tuple2
的 f
参数指定一个特定的顺序,这使得输入工作正常。
# 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>
所以,apply_to_tuple2
的f
参数的类型是second:'a -> first:'b -> 'c
。但是划分的类型是first:int -> second:int -> int
。这些类型无法统一,因为命名参数的顺序在 OCaml 中很重要。
如果您更改了 OCaml 以便命名参数的顺序无关紧要,您可以使这些类型匹配。但这不是 OCaml 现在的工作方式。
此外,如果您确实进行了此更改,则您还必须放弃 OCaml 的功能,从而在某些情况下可以省略参数名称。因此,这将是对语言的不兼容更改。
虽然看起来
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>
一旦 ~a
和 ~b
都被提供,它将打印 "b"
:
# foo2 ~b:();;
b
- : unit = ()
而 bar
在给出标记为 ~a
:
的参数时不会打印任何内容
# let bar2 = bar ~a:();;
val bar2 : b:'a -> unit = <fun>
因为两个打印语句都在 ~b
参数之下。一旦它也被赋予 ~b
参数——这样 ~a
和 ~b
都被提供了——它将打印 "b"
和 "a"
:
# bar2 ~b:();;
b
a
- : unit = ()
像这样保持副作用的正确顺序需要 OCaml 以不同于第二个标记参数的方式处理第一个标记参数:
当 OCaml 看到第一个标记参数的应用时,它必须应用该函数并在其下执行任何副作用。
当 OCaml 看到第二个参数的应用时,它必须构建一个等待第一个参数的新函数,当它接收到时,它将使用第一个和第二个参数应用原始函数参数.
这意味着类型中参数的顺序很重要,您不能简单地使用 second:int -> first:int -> int
值,而应该是 first:int -> second:int -> int
值。
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
我想知道为什么会这样。似乎这里没有任何歧义,编译器可以正确地推断出所有内容吗?
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
您可以为 apply_to_tuple2
的 f
参数指定一个特定的顺序,这使得输入工作正常。
# 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>
所以,apply_to_tuple2
的f
参数的类型是second:'a -> first:'b -> 'c
。但是划分的类型是first:int -> second:int -> int
。这些类型无法统一,因为命名参数的顺序在 OCaml 中很重要。
如果您更改了 OCaml 以便命名参数的顺序无关紧要,您可以使这些类型匹配。但这不是 OCaml 现在的工作方式。
此外,如果您确实进行了此更改,则您还必须放弃 OCaml 的功能,从而在某些情况下可以省略参数名称。因此,这将是对语言的不兼容更改。
虽然看起来
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>
一旦 ~a
和 ~b
都被提供,它将打印 "b"
:
# foo2 ~b:();;
b
- : unit = ()
而 bar
在给出标记为 ~a
:
# let bar2 = bar ~a:();;
val bar2 : b:'a -> unit = <fun>
因为两个打印语句都在 ~b
参数之下。一旦它也被赋予 ~b
参数——这样 ~a
和 ~b
都被提供了——它将打印 "b"
和 "a"
:
# bar2 ~b:();;
b
a
- : unit = ()
像这样保持副作用的正确顺序需要 OCaml 以不同于第二个标记参数的方式处理第一个标记参数:
当 OCaml 看到第一个标记参数的应用时,它必须应用该函数并在其下执行任何副作用。
当 OCaml 看到第二个参数的应用时,它必须构建一个等待第一个参数的新函数,当它接收到时,它将使用第一个和第二个参数应用原始函数参数.
这意味着类型中参数的顺序很重要,您不能简单地使用 second:int -> first:int -> int
值,而应该是 first:int -> second:int -> int
值。
OCaml 可能会尝试在 运行 时将第一个参数的应用程序与其他应用程序区分开来,因此不需要在类型系统中跟踪这一点,但这会使标记函数效率远低于常规函数。