背景:我试图创建一些内容,如文章Data Type à la Carte所述 - 但试图看看OCaml的多态变体是否可以导致一个干净的ReasonML实现。
我的代码在ReasonML语法中,但问题同样适用于OCaml。
我首先为Val
和Add
定义了两个模块,两个都实现了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)
};
};
这将编译并在更大的上下文中工作,我可以评估由Val
和Add
值组成的表达式。
但是,作为一个想要避免编写样板代码的程序员,我的下一步是创建一个可以从任何两个兼容模块生成这样一个模块的仿函数(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.t
和A2.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版本的说明:我在这里使用了bucklescript v.5,它使用了OCaml 4.02编译器。但是也欢迎需要4.06的解决方案(支持应该来到Bucklescript)
你的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]形式中指定,也可以通过类型表达式间接指定,类型表达式必须扩展为精确的变体类型,其标签规范插入其位置。
(Qazxswpoi)
你不需要你的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
模块。