Types OCaml递归类型交叉a";模块类型=";
我有一套复杂的约束条件(主要是教学方面的约束),导致我想做这样的事情:Types OCaml递归类型交叉a";模块类型=";,types,module,ocaml,Types,Module,Ocaml,我有一套复杂的约束条件(主要是教学方面的约束),导致我想做这样的事情: type alpha = ... ENV.env ... and module type ENV = sig type env val foo : ...alpha... end module E1 : M1.ENV = struct module type TMP = sig type tmp_beta (* or some other type from Virtual, whic
type alpha = ... ENV.env ...
and module type ENV = sig
type env
val foo : ...alpha...
end
module E1 : M1.ENV = struct
module type TMP = sig
type tmp_beta (* or some other type from Virtual, which we can't include until after declaring env *)
type env = int -> tmp_beta
include M1.Virtual with type env2 = env
end
(* now we unify tmp_beta with Virtual.beta *)
module rec TMP : TMP with type tmp_beta = TMP.beta = TMP
include TMP
end (* E1 *)
但这是不合法的。首先,您不能将模块类型ENV=
作为递归类型定义的一部分。(我认为甚至没有任何版本的递归声明仅限于模块类型声明。)其次,您不能让ENV.ENV
调用模块类型的组件;您只能编写M.env
,其中M
是一个已实现的模块结构。但是,如果像上面这样的事情是合法的,它将抓住我的目标
这是一个简化的测试用例,展示了我的一些限制
(* M1 is really the contents of an .mli file, not a module *)
module M1 = struct
type env (* I want env to be opaque or abstract at this point ... *)
type alpha = A of int | B of env * int
type beta = C of int | D of alpha
module type ENV = sig
type env (* ...but to be unified with this env *)
val foo : unit -> beta -> unit
val empty : env
end
end
(* M2 needs to be in another_file.ml *)
module M2 = struct
(* here we provide an implementation of M1.ENV *)
module E1 : M1.ENV = struct
type env = char
let foo () _ = ()
let empty = 'X'
end
(* then I'd want this to typecheck, but it doesn't *)
let _ = M1.B(E1.empty, 0)
end
在M1
中,ENV
声明之前的部分需要引用ENV
类型,但是ENV
声明本身需要引用M1
的其他部分中发生的一些情况。所以不清楚哪一个应该放在第一位。如果ENV
不需要引用beta
,而beta又引用alpha
,我可以将ENV
放在文件的开头,并包含ENV
(正如我上面所说,这实际上是一个.mli文件),以便访问ENV
类型以声明alpha
。我不确定这是否真的会导致alpha
中的env
与M1中的env
相同。env
(在OCamlinclude
-ing中,模块类型被称为其内容的文本副本);但无论如何,我不能在这里做,因为相互依赖。因此,我必须在M1
的开头预先声明类型env
。对于我的需求来说,我们不能在M1
中指定env
的实现,这一点至关重要
上面我为M1
给出的声明被OCaml接受,但这两种类型env
并不统一。这并不奇怪,但我的任务是找到一些能将它们统一起来的曲解。当我们稍后为ENV
提供一个实现时,就像上面的M2
一样,我们希望能够使用它来提供M1.alpha
的实例。但目前我们没有:M1.B(E1.empty,0)
不会进行打字检查
现在有一个解决办法。我可以使用M1.alpha
使用类型变量'env
而不是抽象类型env
。但是,M1.alpha
需要在'env
上进行参数化,M1.beta
也需要进行参数化,并且由于我的类型之间的相互依赖性,整个项目中几乎每种类型都需要在'env
类型上进行参数化,这是一个具体的例子,在我们到达建筑链更深处的模块M2
之前,我们无法提供它。这在教学上是不可取的,因为它使所有类型都更难理解,即使在环境没有直接相关性的情况下也是如此
因此,我一直在试图弄清楚,是否有一些技巧可以通过函子或一级模块来实现,这将使我能够获得我在
模块M1
中寻找的那种相互依赖关系,并在稍后的文件中提供env
类型的实现,这里由模块M2
表示。我还没有弄明白这样一件事。我不知道这是否有帮助,但这个小例子对我很有用:
# module rec A : sig type alpha = B.env list end = A
and B : sig type env val foo: A.alpha end =
struct type env = int let foo = [3] end;;
module rec A : sig type alpha = B.env list end
and B : sig type env val foo : A.alpha end
# B.foo
- : A.alpha = [<abstr>]
#模块rec A:sig type alpha=B.env list end=A
和B:sig-type-env-val-foo:A.alpha-end=
结构类型env=int let foo=[3]end;;
模块记录A:sig类型alpha=B.env列表结束
和B:sig-type-env-val-foo:A.alpha-end
#B.foo
-:A.alpha=[]
它的结构似乎让人想起您最初的示例,限制了
alpha
最终被包装在模块中。正如我在评论中所说,@Jeffrey Scofield的回答向我揭示了使用递归模块定义在模块实现中重复模块sig的纯类型部分的好技巧,无需重复。这和一点思考为我的测试用例提供了以下解决方案。这是一个解决方案,这取决于我对构建链中M2
的位置有一定的灵活性,并且我愿意将M1.ENV
作为M1
其余签名的扩展,并让包中的其他文件使用M2
提供的实现,而不是使用M1
。这些都符合我的实际约束条件
(* M1 is really the contents of an .mli file, not a module *)
module M1 = struct
type env (* I want env to be opaque or abstract at this point ... *)
type alpha = A of int | B of env * int
type beta = C of int | D of alpha
module type ENV = sig
type env (* ...but to be unified with this env *)
val foo : unit -> beta -> unit
val empty : env
end
end
(* M2 needs to be in another_file.ml *)
module M2 = struct
(* here we provide an implementation of M1.ENV *)
module E1 : M1.ENV = struct
type env = char
let foo () _ = ()
let empty = 'X'
end
(* then I'd want this to typecheck, but it doesn't *)
let _ = M1.B(E1.empty, 0)
end
诀窍在于做到这一点:
(* M1 is really the contents of an .mli file, not a module *)
module M1 = struct
(* we encapsulate the prefix of M1 in its own sig *)
module type Virtual = sig
type env2 (* an abstract type for now ... *)
type alpha = A of int | B of env2 * int
type beta = C of int | D of alpha
end
module type ENV = sig
type env
(* Now we include Virtual inside ENV, unifying their types
using the standard OCaml method. This makes ENV an
extension of the other parts of M1, rather than a small
standalone sig. But that's OK; see below. *)
include Virtual with type env2 = env
(* now beta is available *)
val foo : unit -> beta -> unit
val empty : env
end
end (* M1 *)
(* M2 needs to be in another_file.ml *)
module M2 = struct
(* here we provide an implementation of E1 *)
module E1 : M1.ENV = struct
type env = char
let foo () _ = ()
let empty = 'X'
(* Here's how we can easily provide all the rest of ENV
that we're now obliged to provide. *)
module rec MX : M1.Virtual with type env2 = env = MX
include MX
end
(* this should be legitimate, and it is! *)
let _ = E1.B(E1.empty, 0)
end (* M2 *)
编辑:在我的实际用例中,我想让类型env
的实现使用M1.Virtual
中的其他类型。我最终需要做这样的事情:
type alpha = ... ENV.env ...
and module type ENV = sig
type env
val foo : ...alpha...
end
module E1 : M1.ENV = struct
module type TMP = sig
type tmp_beta (* or some other type from Virtual, which we can't include until after declaring env *)
type env = int -> tmp_beta
include M1.Virtual with type env2 = env
end
(* now we unify tmp_beta with Virtual.beta *)
module rec TMP : TMP with type tmp_beta = TMP.beta = TMP
include TMP
end (* E1 *)
这是一个非常多的扭曲。但它似乎奏效了。我在这里添加了这项技术,以防其他人可能也需要在OCaml中“向前声明”类型,但由于某种原因,通过通常的递归类型声明无法这样做——就像我需要跨越
模块类型=
障碍一样。谢谢,是的,使用递归模块是我给出的玩具示例的一个选项,但事实并非如此。其中一个原因是这些文件位于不同的文件中。另一个原因是M1实际上是一个.mli文件的主体,所以它是一个模块类型,而不是一个模块。但在我的玩具示例中,我看不到如何将M1声明为模块类型,但仍然在M2中引用其组件。但是你可以对编译的.mli文件这样做。一个真正的模块专家会比我更有帮助。但也许您可以在一个地方定义递归类型,然后在.mli
文件中调用它们?我以前做过(结构比较简单),但是你的回答告诉我你可以做模块rec A:sig。。。结束=A
,