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

时间:2015-03-03 09:53:29

标签: module ocaml dry signature redundancy

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

然而,当你仔细看看时,宣言中有很多冗余:

  • 首先,必须在.mli文件
  • 中公布签名和仿函数
  • 其次,签名必须在.ml文件中完全重复(实际上是否有任何合法的方法与.mli文件不同?)
  • 最后,仿函数本身必须再次重复所有定义以实际实现模块类型

Summa summarum,我得到非抽象类型的3个定义网站(例如,当我想允许模式匹配时)。这完全是荒谬的,因此我认为有一些方法。所以我的问题是双重的:

  1. 有没有办法从.ml文件中的.mli文件重复模块类型,而不必手动编写?例如。类似ppx_import的模块签名?
  2. 有没有办法在.ml文件中的模块中包含模块类型?例如。当模块类型只有一个抽象类型定义时,定义该类型并只复制非抽象类型?

4 个答案:

答案 0 :(得分:1)

  • 您已经可以使用ppx_import进行模块签名。您甚至可以在.ml中使用它来查询相应的.mli。
  • 如果模块仅由模块签名组成,则可以单独定义.mli,不包含任何.ml。通过这种方式,您可以定义一个模块,让我们说Foo_sigs,包含签名并在其他地方使用它。

答案 1 :(得分:1)

可以避免重复类型和模块类型定义,将它们移动到外部.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 toplevel上尝试它通过复制粘贴。

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_deriving)生成代码需要.ml中定义的内容。在这个例子中,由于没有类型定义,情况并非如此。

答案 2 :(得分:0)

在这种特定情况下,您可以跳过.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;;

答案 3 :(得分:0)

添加到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