OCaml递归类型越过"模块类型="

时间:2015-03-18 06:18:19

标签: types module ocaml

我有一套复杂的约束(主要是教学法)导致我想做这样的事情:

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中的alphaenv中的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表示。我还没有能够想出这样的事情。

2 个答案:

答案 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 =障碍。