我正在尝试编写一个不需要处理所有已知类型事件的事件处理程序,并尝试使用OCaml多态变体类型(event.mli
)对其进行建模:< / p>
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
?
答案 0 :(得分:3)
我认为问题出在你的类型定义中,我知道你希望你的类型至多包含这三个事件(首先为什么'最多'而不是'至少'?)但是在这种情况下使用这个签名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
| _ -> ()
答案 1 :(得分:2)
让我们用简单的英语和一些常识来解释“亚型理论”。
在面向对象语言(如java或ocaml)中,您可以定义的最通用的类是空类,即。没有财产或方法的阶级。实际上,任何类都可以从它派生,并且在类型方面,任何类类型都是空类类型的子类型。
现在,functions在输入上被称为逆变,在输出上被称为协变。
如果我们看一下函数的行为方式,我们会看到:
为什么我们使用两个不同的单词来表示输入和输出的相同行为?
好吧,现在考虑ML样式类型理论中常用的类型构造函数:products(*
类型构造函数),sums(代数数据类型)和箭头(函数类型)。基本上,如果使用产品或总和定义类型T,则特殊化(使用子类型)任何参数都将产生T的特化(子类型)。我们将此特征称为协方差。例如,由sums组成的列表构造函数是协变的:如果您有一个类a
的列表,而b
是从a
派生的,那么类型为{{ 1}}是b list
的子类型。实际上,对于接受a list
的函数,您可以传递a list
而不会出错。
如果我们查看 arrow b list
构造函数,那么故事就会略有不同。类型->
的函数f采用x -> y
的任何子类型并返回x
的任何子类型。如果我们假设x是一个函数类型,则意味着f实际上是y
,(u -> v) -> y
= x
。到现在为止还挺好。在这种情况下,u -> v
和u
如何变化?这取决于f可以用它做什么。 f知道它只能将v
或u
的子类型传递给该函数,因此f可以传递的最一般值是u
,这意味着实际的函数是传递可以接受u
的任何超类型作为参数。在极端情况下,我可以给f一个接受空对象作为其参数的函数,并返回u
类型的函数。所以突然间,类型集从“类型和子类型”变为“类型和超类型”。因此,v
类型的子类型为u -> v
,其中u' -> v'
是v'
的子类型,v
是超类型 { {1}}。这就是为什么我们的箭头构造函数被认为是对其输入的逆变。类型构造函数的方差是如何根据其参数的子类型/超类型确定其子类型/超类型。
接下来,我们必须考虑多态变体。如果类型u'
定义为u
,那么与x
= [ `A | `B ]
类型的关系是什么?子类型的主要特性是给定类型的任何值都可以安全地升级为超类型(实际上通常是如何定义子类型)。这里,y
属于这两种类型,因此转换是双向安全的,但[ `A ]
仅存在于第一种类型中,因此可能不会被转换为第二种类型。
因此,`A
的值可以转换为第一个值,但`B
的值可能不会转换为第二个值。子类型关系很明确:y
是x
的子类型!
y
和x
符号怎么样?第一个表示一个类型及其所有超类型(它们是无穷大),而第二个表示一个类型及其所有子类型(这是一个有限集,包括空类型)。因此,对于采用多态变体[> ... ]
的函数而言,自然感染的类型将是对变体及其所有子类型的输入,即。 [< ...]
,但是一个更高阶函数 - 一个以函数作为参数的函数 - 将看到参数方差翻转,因此其输入类型将类似于v
。函数方差的确切规则在上面链接的维基百科页面中表示。
现在你可以看到为什么你的目标类型 - [< v ]
- 无法构建。箭头的差异禁止它。
那么,您可以在代码中做些什么?我的猜测是,您真正想要做的是推理算法将从您的示例代码推导出来的内容:([> v ] -> _) -> _
。其中([< _ ] -> _) -> _)
类型恰好涵盖那里的3个变种。
([> basic_event ] -> _) -> _)
在您的情况下,最好不要在类型中包含下限或上限,并在函数签名中使用这些边界,如上面的代码所述。