在调用函数和回调之间对齐多态变体类型
Align polymorphic variant types between calling function and callback
我正在尝试编写一个不需要处理所有已知类型事件的事件处理程序,并且正在尝试使用 OCaml 多态变体类型对其进行建模 (event.mli
):
type 'events event =
[< `click of int * int | `keypress of char | `quit of int ] as 'events
(* Main loop should take an event handler callback that handles 'less than' all known event types *)
val mainLoop : ('events event -> unit) -> unit
val handler : 'events event -> unit
示例实现 (event.ml
):
type 'events event =
[< `click of int * int | `keypress of char | `quit of int ] as 'events
let mainLoop handler =
for n = 1 to 10 do
handler begin
if n mod 2 = 0 then `click (n, n + 1)
else if n mod 3 = 0 then `keypress 'x'
else `quit 0
end
done
let handler = function
| `click (x, y) -> Printf.printf "Click x: %d, y: %d\n" x y
| `quit code -> exit code
不幸的是,失败并出现以下错误:
File "event.ml", line 1:
Error: The implementation event.ml
does not match the interface event.cmi:
Values do not match:
val mainLoop :
([> `click of int * int | `keypress of char | `quit of int ] -> 'a) ->
unit
is not included in
val mainLoop :
([< `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
File "event.ml", line 4, characters 4-12: Actual declaration
如何将 mainLoop
的实现推断为 ([< `click of int * int | `keypress of char | `quit of int ] -> unit) -> unit
类型,即 ('events event -> unit) -> unit
?
我认为问题出在你的类型定义中,我知道你希望你的类型最多包含这三个事件(首先为什么是 'at most' 而不是 'at least' ?)但是在这个带有 mainLoop
签名的案例,您无法确定您的类型。
例如看 x 的类型:
let (x : [< `A | `B]) = `A
val x : [< `A | `B > `A ] = `A
和[< ... >]
与[< ...]
不是一回事。这意味着即使你投mainLoop
你也会有一个错误:
let mainLoop (handler :
[< `click of int * int | `keypress of char | `quit of int ]
event -> unit) = ...
Values do not match:
val mainLoop :
([ `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
is not included in
val mainLoop :
([< `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
但这真的是个问题吗?为什么不将 type 'events event = [< ...
更改为 type 'events event = [ ...
?
在我看来,使用下限比使用上限更好:
type 'events event =
[> `click of int * int | `keypress of char | `quit of int ] as 'events
val mainLoop : ('events event -> unit) -> unit
val handler : 'events event -> unit
let mainLoop handler =
for n = 1 to 10 do
handler (
if n mod 2 = 0 then `click (n, n + 1)
else if n mod 3 = 0 then `keypress 'x'
else `quit 0
)
done
let handler = function
| `click (x, y) -> Printf.printf "Click x: %d, y: %d\n" x y
| `quit code -> exit code
| _ -> ()
让我们用通俗易懂的语言和一些常识对'subtyping theory'进行一些解释。
在面向对象的语言(如 java 或 ocaml)中,您可以定义的最通用的 class 是空的 class,即。 class 没有 属性 也没有方法。实际上,任何 class 都可以从它派生,并且就类型而言,任何 class 类型都是空 class 类型的子类型。
现在,functions 在输入上是 逆变,在输出上是 协变 。
如果我们查看函数的类型行为,我们会发现:
- 您可以向它传递一个它接受的类型的值,或者该类型的任何子类型的值。在极端情况下,如果您定义一个接受空 class 实例的函数,显然该函数将无法对其执行任何操作。因此,任何其他 class 的实例也可以,因为我们知道该函数不会期望它的任何内容。
- 函数的结果可能是它被定义为 returning 的类型的值,或者是该类型的任何子类型的值。
为什么我们对输入和输出看似相同的行为使用两个不同的词?
好吧,现在考虑 ML 风格类型理论中的常用类型构造函数:products(*
类型构造函数)、sums(代数数据类型)和 arrows(函数类型)。基本上,如果您使用乘积或求和定义类型 T,则对它们的任何参数进行特化(使用其子类型)将产生 T 的特化(子类型)。我们称此特征为 covariance。例如,由求和组成的列表构造函数是协变的:如果您有一个 class a
的列表,并且 b
是从 a
派生的,那么类型 b list
是 a list
的子类型。实际上,对于接受 a list
的函数,您可以毫无错误地传递给它 b list
。
如果我们看一下 箭头 ->
构造函数,情况会略有不同。 x -> y
类型的函数 f 接受 x
的任何子类型和 returns y
的任何子类型。如果我们假设 x 是一个函数类型,这意味着 f 实际上具有类型 (u -> v) -> y
,其中 x
= u -> v
。到目前为止,一切都很好。 u
和 v
在此设置中有何不同?这取决于 f 可以用它做什么。 f 知道它只能将 u
或 u
的子类型传递给该函数,因此 f 可以传递的最一般值是 u
,这意味着实际函数是passed 可以接受 u
的任何超类型作为参数。在极端情况下,我可以给 f 一个函数,它接受空对象作为参数和 return 类型 v
的东西。所以突然之间,类型集从 'type and subtypes' 变成了 'type and supertypes'。所以 u -> v
类型的子类型是 u' -> v'
其中 v'
是 v
的子类型并且 u'
是 超类型 u
个。这就是为什么我们的箭头构造函数据说在其输入上是逆变的。类型构造函数的 方差 是根据其参数的 subtypes/supertypes 确定其 subtypes/supertypes 的方式。
接下来,我们必须考虑多态变体。如果类型 x
被定义为 [ `A | `B ]
,那么与类型 y
= [ `A ]
有什么关系?子类型化的主要 属性 是给定类型的任何值都可以安全地向上转换为超类型(实际上,子类型化通常是这样定义的)。这里,`A
属于两种类型,因此双向转换都是安全的,但 `B
仅出现在第一种类型中,因此可能不会转换为第二种类型。
因此,y
的值可能会转换为第一个,但 x
的值可能不会转换为第二个。子类型关系很清楚:y
是 x
的子类型!
[> ... ]
和 [< ...]
符号呢?第一个表示一个类型及其所有超类型(它们有无穷多个),而第二个表示一个类型及其所有子类型(这是一个有限集,包含空类型)。因此,对于采用多态变体 v
的函数自然推断的类型将在该变体及其所有子类型的输入上,即。 [< v ]
,但是高阶函数——一个以函数作为参数的函数——将看到参数方差翻转,因此它的输入类型将类似于 ([> v ] -> _) -> _
。函数差异的确切规则在上面链接的维基百科页面中表达。
现在您可能明白为什么无法构造您的目标类型 - ([< _ ] -> _) -> _)
。我们的箭头差异禁止它。
那么,您可以在代码中做什么?我的猜测是,您真正想要做的是推理算法将从您的示例代码中推断出的内容:([> basic_event ] -> _) -> _)
。其中 basic_event
类型将恰好涵盖那里的 3 个变体。
type basic_event =
[ `click of int * int | `keypress of char | `quit of int ]
(* Main loop should take an event handler callback that handles 'less than'
all known event types *)
val mainLoop : ([> basic_event ] -> unit) -> unit
val handler : [< `click of int * int | `quit of int ] -> unit
在您的情况下,最好不要在类型中包含下限或上限,并在函数签名中使用这些界限,如上面的代码所述。
我正在尝试编写一个不需要处理所有已知类型事件的事件处理程序,并且正在尝试使用 OCaml 多态变体类型对其进行建模 (event.mli
):
type 'events event =
[< `click of int * int | `keypress of char | `quit of int ] as 'events
(* Main loop should take an event handler callback that handles 'less than' all known event types *)
val mainLoop : ('events event -> unit) -> unit
val handler : 'events event -> unit
示例实现 (event.ml
):
type 'events event =
[< `click of int * int | `keypress of char | `quit of int ] as 'events
let mainLoop handler =
for n = 1 to 10 do
handler begin
if n mod 2 = 0 then `click (n, n + 1)
else if n mod 3 = 0 then `keypress 'x'
else `quit 0
end
done
let handler = function
| `click (x, y) -> Printf.printf "Click x: %d, y: %d\n" x y
| `quit code -> exit code
不幸的是,失败并出现以下错误:
File "event.ml", line 1:
Error: The implementation event.ml
does not match the interface event.cmi:
Values do not match:
val mainLoop :
([> `click of int * int | `keypress of char | `quit of int ] -> 'a) ->
unit
is not included in
val mainLoop :
([< `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
File "event.ml", line 4, characters 4-12: Actual declaration
如何将 mainLoop
的实现推断为 ([< `click of int * int | `keypress of char | `quit of int ] -> unit) -> unit
类型,即 ('events event -> unit) -> unit
?
我认为问题出在你的类型定义中,我知道你希望你的类型最多包含这三个事件(首先为什么是 'at most' 而不是 'at least' ?)但是在这个带有 mainLoop
签名的案例,您无法确定您的类型。
例如看 x 的类型:
let (x : [< `A | `B]) = `A
val x : [< `A | `B > `A ] = `A
和[< ... >]
与[< ...]
不是一回事。这意味着即使你投mainLoop
你也会有一个错误:
let mainLoop (handler :
[< `click of int * int | `keypress of char | `quit of int ]
event -> unit) = ...
Values do not match:
val mainLoop :
([ `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
is not included in
val mainLoop :
([< `click of int * int | `keypress of char | `quit of int ] event ->
unit) ->
unit
但这真的是个问题吗?为什么不将 type 'events event = [< ...
更改为 type 'events event = [ ...
?
在我看来,使用下限比使用上限更好:
type 'events event =
[> `click of int * int | `keypress of char | `quit of int ] as 'events
val mainLoop : ('events event -> unit) -> unit
val handler : 'events event -> unit
let mainLoop handler =
for n = 1 to 10 do
handler (
if n mod 2 = 0 then `click (n, n + 1)
else if n mod 3 = 0 then `keypress 'x'
else `quit 0
)
done
let handler = function
| `click (x, y) -> Printf.printf "Click x: %d, y: %d\n" x y
| `quit code -> exit code
| _ -> ()
让我们用通俗易懂的语言和一些常识对'subtyping theory'进行一些解释。
在面向对象的语言(如 java 或 ocaml)中,您可以定义的最通用的 class 是空的 class,即。 class 没有 属性 也没有方法。实际上,任何 class 都可以从它派生,并且就类型而言,任何 class 类型都是空 class 类型的子类型。
现在,functions 在输入上是 逆变,在输出上是 协变 。
如果我们查看函数的类型行为,我们会发现:
- 您可以向它传递一个它接受的类型的值,或者该类型的任何子类型的值。在极端情况下,如果您定义一个接受空 class 实例的函数,显然该函数将无法对其执行任何操作。因此,任何其他 class 的实例也可以,因为我们知道该函数不会期望它的任何内容。
- 函数的结果可能是它被定义为 returning 的类型的值,或者是该类型的任何子类型的值。
为什么我们对输入和输出看似相同的行为使用两个不同的词?
好吧,现在考虑 ML 风格类型理论中的常用类型构造函数:products(*
类型构造函数)、sums(代数数据类型)和 arrows(函数类型)。基本上,如果您使用乘积或求和定义类型 T,则对它们的任何参数进行特化(使用其子类型)将产生 T 的特化(子类型)。我们称此特征为 covariance。例如,由求和组成的列表构造函数是协变的:如果您有一个 class a
的列表,并且 b
是从 a
派生的,那么类型 b list
是 a list
的子类型。实际上,对于接受 a list
的函数,您可以毫无错误地传递给它 b list
。
如果我们看一下 箭头 ->
构造函数,情况会略有不同。 x -> y
类型的函数 f 接受 x
的任何子类型和 returns y
的任何子类型。如果我们假设 x 是一个函数类型,这意味着 f 实际上具有类型 (u -> v) -> y
,其中 x
= u -> v
。到目前为止,一切都很好。 u
和 v
在此设置中有何不同?这取决于 f 可以用它做什么。 f 知道它只能将 u
或 u
的子类型传递给该函数,因此 f 可以传递的最一般值是 u
,这意味着实际函数是passed 可以接受 u
的任何超类型作为参数。在极端情况下,我可以给 f 一个函数,它接受空对象作为参数和 return 类型 v
的东西。所以突然之间,类型集从 'type and subtypes' 变成了 'type and supertypes'。所以 u -> v
类型的子类型是 u' -> v'
其中 v'
是 v
的子类型并且 u'
是 超类型 u
个。这就是为什么我们的箭头构造函数据说在其输入上是逆变的。类型构造函数的 方差 是根据其参数的 subtypes/supertypes 确定其 subtypes/supertypes 的方式。
接下来,我们必须考虑多态变体。如果类型 x
被定义为 [ `A | `B ]
,那么与类型 y
= [ `A ]
有什么关系?子类型化的主要 属性 是给定类型的任何值都可以安全地向上转换为超类型(实际上,子类型化通常是这样定义的)。这里,`A
属于两种类型,因此双向转换都是安全的,但 `B
仅出现在第一种类型中,因此可能不会转换为第二种类型。
因此,y
的值可能会转换为第一个,但 x
的值可能不会转换为第二个。子类型关系很清楚:y
是 x
的子类型!
[> ... ]
和 [< ...]
符号呢?第一个表示一个类型及其所有超类型(它们有无穷多个),而第二个表示一个类型及其所有子类型(这是一个有限集,包含空类型)。因此,对于采用多态变体 v
的函数自然推断的类型将在该变体及其所有子类型的输入上,即。 [< v ]
,但是高阶函数——一个以函数作为参数的函数——将看到参数方差翻转,因此它的输入类型将类似于 ([> v ] -> _) -> _
。函数差异的确切规则在上面链接的维基百科页面中表达。
现在您可能明白为什么无法构造您的目标类型 - ([< _ ] -> _) -> _)
。我们的箭头差异禁止它。
那么,您可以在代码中做什么?我的猜测是,您真正想要做的是推理算法将从您的示例代码中推断出的内容:([> basic_event ] -> _) -> _)
。其中 basic_event
类型将恰好涵盖那里的 3 个变体。
type basic_event =
[ `click of int * int | `keypress of char | `quit of int ]
(* Main loop should take an event handler callback that handles 'less than'
all known event types *)
val mainLoop : ([> basic_event ] -> unit) -> unit
val handler : [< `click of int * int | `quit of int ] -> unit
在您的情况下,最好不要在类型中包含下限或上限,并在函数签名中使用这些界限,如上面的代码所述。