签名/函数模式中的非抽象类型冗余



使用Signature/Functor模式,我参考OCaml标准库中Map.S/Map.Make的样式。当您希望在某种类型上参数化一大块代码而不使其完全多态时,这种模式非常成功。基本上,您通过提供签名(通常称为S)和构造函数(Make)来引入参数化模块。

然而,当你仔细观察时,声明中有很多冗余:

  • 首先,签名和函子都必须在.mli文件中公布
  • 其次,签名必须在.ml文件中完全重复(这里是否有任何不同于.mli文件的合法方法?)
  • 最后,函子本身必须再次重复所有定义才能真正实现模块类型

求和,我得到了3个非抽象类型的定义站点(例如,当我想允许模式匹配时)。这完全是荒谬的,因此我认为这是有办法的。所以我的问题有两个:

  1. 有没有一种方法可以在.ml文件中重复.mli文件中的模块类型,而不必手动写入?例如,模块签名的ppx_import之类的东西
  2. 有没有办法将模块类型包含在.ml文件中的模块中?例如,当模块类型只有一个抽象类型定义时,定义该类型并复制非抽象类型
  • 您已经可以对模块签名使用ppx_import。您甚至可以在.ml中使用它来查询相应的.mli
  • 如果模块仅由模块签名组成,则可以单独定义.mli,而不定义任何.ml。通过这种方式,可以定义包含签名的模块,比如Foo_sigs,并在其他任何地方使用它

可以避免重复类型和模块类型定义,将它们移动到外部.ml文件中。让我们看看下面的例子:

module M : sig
  (* m.mli *)    
  module type S = sig 
    type t 
    val x : t 
  end
  module type Result = sig
    type t
    val xs : t list
  end
  module Make(A : S) : Result with type t = A.t
end = struct
  (* m.ml *)
  module type S = sig 
    type t 
    val x : t 
  end
  module type Result = sig
    type t
    val xs : t list
  end
  module Make(A : S) = struct
    type t = A.t
    let xs = [A.x;A.x]
  end
end

我没有编写两个文件m.mlim.ml,而是使用了一个具有显式签名的模块M:这相当于拥有这两个文件,您可以通过复制和粘贴在OCaml顶层进行尝试。

M中,事物在sig .. endstruct .. end中被欺骗。如果模块类型变大,这会很麻烦。

您可以通过将这些重复数据移动到另一个.ml文件来共享它们。例如,类似以下n_intf.ml:

module N_intf = struct
  (* n_intf.ml *)    
  module type S = sig 
    type t 
    val x : t 
  end
  module type Result = sig
    type t
    val xs : t list
  end
end
module N : sig
  (* n.mli *)
  open N_intf
  module Make(A : S) : Result with type t = A.t
end = struct
  (* n.ml *)
  open N_intf
  module Make(A : S) = struct
    type t = A.t
    let xs = [A.x;A.x]
  end
end

您也可以使用*_intf.mli而不是*_intf.ml,但我建议使用*_intf.ml,因为:

  • 模块包装不考虑仅mli模块,因此您必须在安装时复制*_intf.cmi
  • 从类型定义(如ppx_dering)生成代码需要.ml中定义的东西。在本例中,情况并非如此,因为没有类型定义

在这种特定情况下,您可以跳过.mli部分:

  • 你的抽象是由.ml指定的
  • 阅读它会让它变得非常清楚(正如人们从stdlib中知道的那样)
  • 你放在.mli里的所有东西都已经在.ml里了

如果你在一个需要你实际给出mli的小组中工作,只需使用ocamlc -i技巧自动生成它。

ocamlc -i m.ml >m.mli # automatically generate mli from ml

我知道它不能完全回答你的问题,但嘿,它解决了你的问题。

我知道总是放一个mli被认为是最好的做法,但它不是强制性的,这可能有一些很好的原因。

至于你的第二个问题,我不确定我是否理解,但我认为这回答了它:

module type ToCopy = sig type t val f : t -> unit end
module type Copy1 = sig include ToCopy with type t = int end
module type Copy2 = ToCopy with type t = int;;

添加到camlspoter的答案中,由于问题提到了模式匹配,您可能需要"重新导出"N_intf中声明的带有构造函数的签名和类型,以便可以通过N访问它们。在这种情况下,您可以将open替换为includemodule type of,即:

module N_intf = struct
  type t = One | Two
  (* n_intf.ml *)    
  module type S = sig 
    type t 
    val x : t 
  end
  module type Result = sig
    type t
    val xs : t list
  end
end
module N : sig
  (* n.mli *)
  include module type of N_intf
  module Make(A : S) : Result with type t = A.t
end = struct
  (* n.ml *)
  include N_intf
  module Make(A : S) = struct
    type t = A.t
    let xs = [A.x;A.x]
  end
end

然后你会得到以下签名:

module N_intf : 
  sig 
    type t = One | Two 
    module type S = sig type t val x : t end 
    module type Result = sig type t val xs : t list end
  end
module N : 
  sig         
    type t = One | Two
    module type S = sig type t val x : t end
    module type Result = sig type t val xs : t list end
    module Make : functor (A : S) -> sig type t = A.t val xs : t list end
  end

现在,构造函数OneTwo可以由N而不是N_intf限定,因此您可以在程序的其余部分中忽略N_intf

最新更新