将模块及其实例作为 OCaml 函数的参数

Having a module and an instance of it as parameters to an OCaml function

我想编写一个函数,它采用实现特定签名的模块和与这些模块相同类型的实例,但显然我不能这样做,因为与模块范围相关的问题(模块和它的实例都是参数,因此实例不知道模块的类型。

这是一个例子:

let f (type a) (module M: Set.S with type elt = a) (pr: a -> unit) (m: M.t) = 
  M.iter pr m;;

其中 M 是具有 a 类型元素的 Set 模块,pr 可以是 a 类型元素的打印机。 以及由此引起的错误消息(我觉得不是很清楚):

Line 1, characters 69-78:
Error: This pattern matches values of type M.t
       but a pattern was expected which matches values of type 'a
       The type constructor M.t would escape its scope

我试图解决这个问题,考虑到问题是由参数的范围仅覆盖函数体引起的,所以我将最后一个参数放在函数体中,如下所示:

let f (type a) (module M: Set.S with type elt = a) (pr : a -> unit) =
  fun (m : M.t) ->
    M.iter pr m;;

但错误信息仍然存在:

Line 2, characters 7-16:
Error: This pattern matches values of type M.t
       but a pattern was expected which matches values of type 'a
       The type constructor M.t would escape its scope

那么有什么办法吗?

OCaml 核心语言(在模块系统之外)不是依赖类型的。在幻想语法中,您的函数将具有类型 function (module M: Set.S with type elt = 'a) -> ('a -> unit) -> M.t。在这种类型中,M是一个值,因此该类型是依赖类型的,不能在OCaml中实现。

在您的情况下,可以通过使用 with 约束

限制接受作为参数的模块的 class 来使类型不依赖
let f (type a t ) (module M: Set.S with type elt = a and type t = t)
  pr m = M.iter pr m
module String_set = Set.Make(String)
let () = f (module String_set) ignore String_set.empty 

另一种可能的解决方案是将值与第一个 class 模块及其存在量化一起存储:


module type packed = sig
  type a
  module Impl: Set.S with type elt = a
  val value: Impl.t
end

let g (type a) (module P: packed with type a = a)
  pr = P.Impl.iter pr P.value

但是对于更复杂的函数,除了在模块级别使用函子之外别无选择。

旁白:如果您想知道为什么上面第一个变体中的模块类型 Set.S with type elt = a and type t = t 是一个(必要的)限制,请考虑这个打包模块:

let random_int_set: (module Set.S with type elt = int) =
  let compare =
     if Random.int 3 > 1 then Stdlib.compare
     else (fun x y -> Stdlib.compare y x)
  in
  let module S = Set.Make(struct type t = int let compare = compare end) in
  (module S)

这里的集合类型是基于一个随机的compare函数。因此集合的类型与所有其他 Set 不兼容。因此,只能使用具有打包值的此类模块:

module P = struct
  type a = int
  module Impl = (val random_int_set)
  let value = Impl.empty
end
let () = g (module P) ignore