OCaml 递归类型跨越 "module type ="
OCaml recursive types crossing a "module type ="
我有一组复杂的约束(主要是教学方面的)导致我想做这样的事情:
type alpha = ... ENV.env ...
and module type ENV = sig
type env
val foo : ...alpha...
end
但这不是合法的 OCaml。首先,您不能将 module type ENV =
作为递归类型定义的一部分。 (我认为甚至没有任何版本的递归声明仅限于模块类型声明。)对于两个,你不能 ENV.env
调用模块的组件 type;你只能写 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
(正如我上面所说,这实际上是一个 .mli 文件)以便访问 alpha
声明的 env
类型。我不确定这是否真的会导致 alpha
中的 env
与 M1.ENV
中的 env
相同(在 OCaml include
-ing a据说模块类型只是其内容的文本副本);但无论如何,由于相互依赖性,我不能在这里做。所以我必须在 M1
的开头预先声明 type 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
类型进行参数化,这是一个具体的实例,直到我们到达 module M2
,在构建链的更下方才能提供。这在教学上是不可取的,因为它使所有类型都更难理解,即使在环境没有直接相关性的情况下也是如此。
所以我一直在试图弄清楚是否有一些技巧我可以用仿函数或 first-class 模块来执行,这将使我能够在 module M1
,并在后面的文件中提供 env
类型的实现,这里用 module 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>]
它的结构似乎让人想起你最初的例子,但限制是 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
(* 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 中输入 "forward-declare" 类型,但由于某些原因无法通过通常的递归类型声明这样做——就像我需要跨越module type =
障碍。
我有一组复杂的约束(主要是教学方面的)导致我想做这样的事情:
type alpha = ... ENV.env ...
and module type ENV = sig
type env
val foo : ...alpha...
end
但这不是合法的 OCaml。首先,您不能将 module type ENV =
作为递归类型定义的一部分。 (我认为甚至没有任何版本的递归声明仅限于模块类型声明。)对于两个,你不能 ENV.env
调用模块的组件 type;你只能写 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
(正如我上面所说,这实际上是一个 .mli 文件)以便访问 alpha
声明的 env
类型。我不确定这是否真的会导致 alpha
中的 env
与 M1.ENV
中的 env
相同(在 OCaml include
-ing a据说模块类型只是其内容的文本副本);但无论如何,由于相互依赖性,我不能在这里做。所以我必须在 M1
的开头预先声明 type 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
类型进行参数化,这是一个具体的实例,直到我们到达 module M2
,在构建链的更下方才能提供。这在教学上是不可取的,因为它使所有类型都更难理解,即使在环境没有直接相关性的情况下也是如此。
所以我一直在试图弄清楚是否有一些技巧我可以用仿函数或 first-class 模块来执行,这将使我能够在 module M1
,并在后面的文件中提供 env
类型的实现,这里用 module 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>]
它的结构似乎让人想起你最初的例子,但限制是 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
(* 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 中输入 "forward-declare" 类型,但由于某些原因无法通过通常的递归类型声明这样做——就像我需要跨越module type =
障碍。