我有一套复杂的约束(主要是教学法)导致我想做这样的事情:
type alpha = ... ENV.env ...
and module type ENV = sig
type env
val foo : ...alpha...
end
但那不是合法的OCaml。首先,您不能将module type 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
,而alpha
又引用ENV
,我可以将include ENV
放在文件的开头{{1} (如上所述,这实际上是一个.mli文件),以便能够访问env
类型的alpha
类型。我不确定这是否真的会导致env
中的alpha
与env
中的M1.ENV
相同(在OCaml include
中 - 据说模块类型只是其内容的文本副本);但无论如何,由于相互依赖,我不能在这里做。所以我必须在type env
开头预先声明M1
。我的需求至关重要,因为M1
我们无法为env
指定实施。
我在上面提到的M1
声明已被OCaml接受,但env
这两种类型并未统一。这并不奇怪,但我的任务是找到一些可以统一它们的扭曲。当我们稍后为ENV
提供实施时,如上面的M2
,我们希望能够使用它来提供M1.alpha
的实例。但目前我们还没有:M1.B(E1.empty, 0)
无法获得类型检查。
现在有一个解决方案。我可以让M1.alpha
使用类型变量'env
而不是抽象类型env
。但是M1.alpha
需要在'env
上进行参数化,然后M1.beta
也需要参数化,并且由于我的类型中的相互依赖性,几乎整个项目中的每个类型都需要在'env
类型上进行参数化,这是我们在到达module M2
之前无法提供的具体实例,在构建链中进一步向下。这在教学上是不合需要的,因为它使所有类型都难以理解,即使在环境没有直接相关性的环境中也是如此。
所以我一直试图弄清楚我是否可以使用仿函数或使用一流模块进行一些技巧,这些技巧可以让我获得我正在寻找的各种相互依赖关系在module M1
中,并在稍后的文件中提供env
类型的实现,此处由module M2
表示。我还没有能够想出这样的事情。
答案 0 :(得分:3)
我不知道这是否有用,但这个小例子对我有用:
# 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>]
似乎有一个让人联想到你的初始例子的结构,alpha
最终包含在模块中的限制。
答案 1 :(得分:1)
正如我在评论中所说,@ Jeffrey-Scofield的答案向我揭示了使用递归模块定义在模块实现中重复模块sig的仅类型部分的好技巧,而无需重复它。这个和一点思考给了我以下解决方案来测试我的测试用例。这是一个解决方案,我可以灵活地了解构建链M2
的位置,并且我愿意让M1.ENV
成为M1
其余签名的扩展。 ,并且包中的其他文件使用M2
提供的实现,而不是使用M1
。这些都与我的实际约束兼容。
诀窍是这样做:
(* 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
中的其他类型。我最终需要做这样的事情:
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中“转发声明”类型,但由于某种原因而无法通过通常的递归类型声明这样做 - 因为我需要跨越一个module type =
障碍。