背景:我正在尝试创建论文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版本的注释:我在这里使用的是v.5扣式脚本,它使用了OCaml 4.02编译器。但是也欢迎需要4.06的解决方案(应该支持Bucklescript)
答案 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
模块。