【问题标题】:OCaml recursive types crossing a "module type ="跨越“模块类型=”的OCaml递归类型
【发布时间】:2015-03-18 06:18:19
【问题描述】:

我有一组复杂的限制(主要是教学),导致我想做这样的事情:

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的声明本身需要引用@其他部分发生的一些事情987654331@。所以不清楚哪个应该先出现。如果ENV 不需要引用beta,而后者又引用alpha,我可以将ENV 放在文件的开头和include ENV(正如我上面所说,这真的是一个.mli 文件),以便访问 env 类型以声明 alpha。我不确定这是否真的会导致alpha 中的envM1.ENV 中的env 相同(在OCaml include 中,模块类型据说只是一个文本副本其内容);但无论如何,由于相互依赖,我不能在这里做。所以我必须在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,我们才能提供它,在构建链的更下方。这在教学上是不可取的,因为它使所有类型都更难理解,即使在环境没有直接相关性的情况下也是如此。

所以我一直在试图弄清楚是否有一些技巧可以用函子或一流的模块执行,使我能够获得我在module M1 中寻找的那种相互依赖关系,并提供一个在后面的文件中实现env 类型,这里用module M2 表示。我还没有弄清楚这样的事情。

【问题讨论】:

    标签: types module ocaml


    【解决方案1】:

    我不知道这是否有帮助,但这个小例子对我有用:

    # 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 最终包装在一个模块中。

    【讨论】:

    • 谢谢,是的,使用递归模块是我给出的玩具示例的一个选项,但实际上并非如此。一个原因是它们存在于不同的文件中。另一个原因是 M1 实际上是 .mli 文件的主体,因此它是模块类型而不是模块。但是在我的玩具示例中,我看不到如何将 M1 声明为模块类型,但仍然在 M2 中引用它的组件。但是您可以对已编译的 .mli 文件执行此操作。
    • 真正的模块专家会比我更有帮助。但是也许你可以在一个地方定义你的递归类型,然后在你的.mli文件中调用它们?我过去做过这个(结构更简单)。
    • 但是你的回答确实告诉我你可以做到module rec A : sig ... end = A,当你希望A 拥有的唯一内容是类型时。这很有趣,似乎很有用。谢谢。
    【解决方案2】:

    正如我在评论中所说,@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 =屏障。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-01
      • 2018-07-01
      • 2015-06-10
      • 2017-09-09
      • 1970-01-01
      • 1970-01-01
      • 2016-02-02
      相关资源
      最近更新 更多