背景:我正在尝试创建论文中描述的内容,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
可以是任何东西,我无法将它们组合为多态变体。
Error: The type A1.t('t) is not a polymorphic variant type
我尝试向 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
}
现在我得到了编译器错误
Error: The type A1.t([> ]) is not a polymorphic variant type
有什么方法可以创建一个自动基于这两个模块的模块仿函数?
关于 OCaml 版本的注意事项:我在这里使用的是 bucklescript v. 5,它使用 OCaml 4.02 编译器。但也欢迎需要 4.06 的解决方案(Bucklescript 应该会支持)
最佳答案
您的Functor
签名定义了一个抽象类型'a t
。正如您正确指出的那样,“由于 A1.t 和 A2.t 可以是任何东西,我不能将它们组合为多态变体。”为了解决 Functor
中的 'a t
是抽象的问题,您尝试通过以下方式使其成为多态变体:
module Functor = struct
type 'a t = 'a constraint 'a = [< ]
end
但是,'a
类型变量不再代表包装值,而是代表多态变体约束。这当然不是你想要的。您收到错误 Error: The type A1.t([> ]) is not a polymorphic variant type
因为您只能将“精确变体类型”替换为多态变体:
The first case is an exact variant type: all possible tags are known, with their associated types, and they can all be present. Its structure is fully known.
...
In all three cases, tags may be either specified directly in the `tag-name [of typexpr] form, or indirectly through a type expression, which must expand to an exact variant type, whose tag specifications are inserted in its place.
( 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
好处是 Functor
中的 'a t
仍然是抽象的,代码适用于未定义多态变体的 Functor
模块。
关于ocaml - 为具有泛型类型参数的多态变体编写类型约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55828648/