2

我正在尝试编写一个不需要处理所有已知类型的事件的事件处理程序,并尝试使用 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推断为 type ([< `click of int * int | `keypress of char | `quit of int ] -> unit) -> unit,即 as ('events event -> unit) -> unit

4

2 回答 2

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
  | _ -> ()
于 2017-09-04T10:55:46.790 回答
3

让我们用简单的英语解释一下“子类型化理论”,以及一点常识。

在面向对象的语言(如 java 或 ocaml)中,您可以定义的最通用的类​​是空类,即。没有属性也没有方法的类。事实上,任何类都可以从它派生,就类型而言,任何类类型都是空类类型的子类型。

现在,我们说函数在输入上是逆变的,在输出上是协变的。

如果我们查看函数在类型方面的行为方式,我们会看到:

  • 您可以将其接受的类型的值或该类型的任何子类型的值传递给它。在极端情况下,如果您定义一个接受空类实例的函数,显然该函数将无法对它做任何事情。因此,任何其他类的实例也可以,因为我们知道该函数不会期望它的任何内容。
  • 函数的结果可以是它定义为返回的类型的值,也可以是该类型的任何子类型的值。

为什么我们使用两个不同的词来表示看似相同的输入和输出行为?

好吧,现在考虑一下 ML 风格类型理论中常见的类型构造函数:products(*类型构造函数)、sums(代数数据类型)和 arrows(函数类型)。基本上,如果您使用乘积或总和定义类型 T,则专门化(使用其子类型)它们的任何参数都会产生 T 的专门化(子类型)。我们将此特征称为协方差。例如,由 sum 组成的列表构造函数是协变的:如果您有一个 class 列表a,并且b派生自a,那么该类型b list是 的子类型a list。实际上,对于接受 a 的函数,a list您可以传递它 ab list而不会出错。

如果我们查看箭头 ->构造函数,情况会略有不同。类型为f 的函数x -> y接受 的任何子类型x并返回 的任何子类型y。如果我们假设 x 是一个函数类型,这意味着 f 实际上具有 type (u -> v) -> y,其中x= u -> v。到现在为止还挺好。在这种情况下可能uv发生什么变化?这取决于 f 可以用它做什么。f 知道它只能将 au或 of 的子类型传递u给该函数,因此 f 可以传递的最一般的值是 a u,这意味着传递的实际函数可以接受任何超类型 ofu作为它的参数。在极端情况下,我可以给出 fa 函数,它接受空对象作为其参数并返回一些类型v. 所以突然之间,类型集从“类型和子类型”变为“类型和超类型”。因此,类型的子类型u -> vu' -> v'wherev'的子类型,v并且u'是 的超类型u这就是为什么我们的箭头构造函数在其输入上是逆变的。类型构造函数的变化是如何根据其参数的子类型/超类型确定其子类型/超类型。

接下来,我们必须考虑多态变体。如果 typex定义为[ `A | `B ],那么与 type y=有什么关系[ `A ]?子类型的主要属性是给定类型的任何值都可以安全地向上转换为超类型(实际上它通常是定义子类型的方式)。这里,`A属于这两种类型,因此强制转换是双向安全的,但`B只存在于第一种类型中,因此可能不会强制转换为第二种。因此,值 ofy可能会被强制转换为第一个,但值x可能不会被强制转换为第二个。子类型关系很明确:yx!的子类型

[> ... ][< ...]符号呢?第一个表示一个类型及其所有超类型(它们有无穷多个),而第二个表示一个类型及其所有子类型(这是一个有限集,包括空类型)。因此,为接受多态变体的函数自然推断的类型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

在您的情况下,最好不要在类型中包含下限或上限,并在函数签名中使用这些边界,如上面的代码中所述。

于 2017-09-05T13:49:38.350 回答