Module 签名/函子模式中的非抽象类型冗余
对于签名/函子模式,我指的是OCaml标准库中的Module 签名/函子模式中的非抽象类型冗余,module,ocaml,dry,signature,redundancy,Module,Ocaml,Dry,Signature,Redundancy,对于签名/函子模式,我指的是OCaml标准库中的Map.S/Map.Make。当您希望在某种类型上参数化一大块代码而不使其完全多态时,此模式非常成功。基本上,通过提供签名(通常称为S)和构造函数(Make)来引入参数化模块 但是,仔细看一下,声明中有很多冗余: 首先,签名和函子都必须在.mli文件中声明 其次,签名必须在.ml文件中完全重复(这里是否有与.mli文件不同的法律方式?) 最后,函子本身必须再次重复所有定义才能真正实现模块类型 SummaSummarum,我得到了3个非抽象类型的
Map.S
/Map.Make
。当您希望在某种类型上参数化一大块代码而不使其完全多态时,此模式非常成功。基本上,通过提供签名(通常称为S
)和构造函数(Make
)来引入参数化模块
但是,仔细看一下,声明中有很多冗余:
- 首先,签名和函子都必须在.mli文件中声明
- 其次,签名必须在.ml文件中完全重复(这里是否有与.mli文件不同的法律方式?)
- 最后,函子本身必须再次重复所有定义才能真正实现模块类型
- 您已经可以将ppx_导入用于模块签名。您甚至可以在.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.mli
和m.ml
,而是使用了一个带有明确签名的模块m
:这相当于有两个文件,您可以通过复制和粘贴在OCaml顶级上进行尝试
在M
中,事物在sig中被复制。。结束
和结构。。结束
。如果模块类型变得更大,这将非常麻烦
您可以通过将这些副本移动到另一个.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
您也可以使用*\u intf.mli
而不是*\u intf.ml
,但我建议使用*\u intf.ml
,因为:
- 模块打包不考虑
仅考虑模块,因此您必须在安装时复制mli
李>*\u intf.cmi
- 从类型定义(如ppx_派生)生成代码需要在
.ml
中定义的内容。在本例中,情况并非如此,因为没有类型定义
- 您的抽象由.ml指定
- 阅读它使它变得非常清晰(因为人们从stdlib中知道这种模式)
- 您在.mli中输入的所有内容都已在.ml中
ocamlc-i
技巧自动生成mli即可
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
访问它们。在这种情况下,您可以将打开的
替换为包含的
和模块类型的
,即:
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
现在构造函数
One
和Two
可以通过N
而不是N\u intf
进行限定,因此您可以忽略程序其余部分中的N\u intf
。我最近尝试了第二个选项,但未能使用ocamlbuild进行构建。您能否发布一个使用ocamlbuild构建的最简单的工作示例?您能否为第一个选项提供一个示例?如何从.mli导入签名?对于第一个选项,camlspotter回答。假设在相同的.ml中有其他值,我要约束其签名。我不能把它们放在一个.mli中,然后在.mli中省略有问题的模块M的签名,因为那样会把M隐藏在外面,对吗?因此,在这种情况下,解决方案似乎并不理想?正确,您可以将其分为两个不同的文件(或者通过-i
转储自动获取所需的mli部分)。我同意一些冗余避免是有用的,但在大多数情况下,它可以通过简单的解决办法来处理。