具有通用类型参数的多态变体的写类型约束

时间:2019-04-24 11:09:05

标签: ocaml reason

背景:我正在尝试创建论文Data Type à la Carte中所述的内容-但试图查看OCaml的多态变体是否可以导致干净的ReasonML实现。

这里的代码是使用ReasonML语法的,但是这个问题同样适用于OCaml。

我首先为ValAdd定义两个模块,两个模块都实现了fmap-使它们成为haskel样式的函子。

module type Functor = {
  type t('a);
  let fmap: ('a => 'b, t('a)) => t('b);
};

module Val = {
  type t('e) = [ | `Val(int)];
  let fmap = _ =>
    fun
    | `Val(x) => `Val(x);
};

module Add = {
  type t('e) = [ | `Add('e, 'e) ];

  let fmap = f =>
    fun
    | `Add(x, y) => `Add((f(x), f(y)))
};

我可以很容易地创建一个Algebra数据类型,并通过一个非常简单的fmap实现将这两个模块组合为一个。

module Algebra = {
  type t('t) = [ Val.t('t) | Add.t('t)];

  let fmap = (f, x) =>
    switch (x) {
    | #Val.t as v => Val.fmap(f, v)
    | #Add.t as o => Add.fmap(f, o)
    };
};

这可以在更大的上下文中进行编译和工作,在这里我可以评估由ValAdd值组成的表达式。

但是,作为一个希望避免编写样板代码的程序员,我的下一步是创建一个函子(OCaml函子),该函子可以从任何两个兼容模块中生成这样的模块。

我的第一次尝试是

module JoinAlgebra = (A1: Functor, A2: Functor) => {
  type t('t) = [ A1.t('t) | A2.t('t)];

  let fmap = (f, x) =>
    switch (x) {
    | #A1.t as v => Val.fmap(f, v)
    | #A2.t as o => Add.fmap(f, o)
    };
};

但这不起作用。由于A1.tA2.t可以是任何东西,因此我无法将它们组合为多态变体。

  

错误:类型A1.t('t)不是多态变体类型

我尝试向Functor模块类型添加类型约束:

module type Functor = {
  type t('a) = 'a constraint [> ] = 'a;
  let fmap: ('a => 'b, t('a)) => t('b);
};

module JoinAlgebra = (A1: Functor, A2: Functor) => {
  type t('t) = [ A1.t('t) | A2.t('t)]; // This line fails
}

现在我收到编译器错误

  

错误:类型A1.t([>])不是多态变体类型

有什么方法可以创建一个基于两个模块的模块功能器吗?

有关OCaml版本的注释:我在这里使用的是v.5扣式脚本,它使用了OCaml 4.02编译器。但是也欢迎需要4.06的解决方案(应该支持Bucklescript)

1 个答案:

答案 0 :(得分:2)

您的Functor签名定义了抽象类型'a t。正如您正确指出的那样,“由于A1.t和A2.t可以是任何东西,因此我不能将它们组合为多态变体。”为了解决'a t中的Functor是抽象的问题,您可以尝试通过以下方式使其成为多态变体:

module Functor = struct
  type 'a t = 'a constraint 'a = [< ]
end

但是,'a类型变量不再代表包装值,而是多态变量约束。这当然不是您想要的。您会收到错误Error: The type A1.t([> ]) is not a polymorphic variant type,因为您只能将“精确变体类型”替换为多态变体:

  

第一种情况是确切的变体类型:已知所有可能的标记及其相关类型,并且它们都可以出现。其结构是众所周知的。

     

...

     

在所有这三种情况下,标签都可以直接以`[tag_name [typexpr]格式]指定,也可以通过类型表达式间接指定,该类型表达式必须扩展为确切的变体类型,其标签说明将插入其位置

https://caml.inria.fr/pub/docs/manual-ocaml/types.html#polymorphic-variant-type

JoinAlgebra不需要多态变体。只要做:

module JoinAlgebra (A1 : Functor) (A2 : Functor) = struct
  type 't t = Left of 't A1.t | Right of 't A2.t

  let fmap f x =
    match x with
    | Left v -> Left (A1.fmap f v)
    | Right o -> Right (A2.fmap f o)
end

好处是'a t中的Functor仍然是抽象的,并且该代码可用于未定义多态变体的Functor模块。