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

Module 签名/函子模式中的非抽象类型冗余,module,ocaml,dry,signature,redundancy,Module,Ocaml,Dry,Signature,Redundancy,对于签名/函子模式,我指的是OCaml标准库中的Map.S/Map.Make。当您希望在某种类型上参数化一大块代码而不使其完全多态时,此模式非常成功。基本上,通过提供签名(通常称为S)和构造函数(Make)来引入参数化模块 但是,仔细看一下,声明中有很多冗余: 首先,签名和函子都必须在.mli文件中声明 其次,签名必须在.ml文件中完全重复(这里是否有与.mli文件不同的法律方式?) 最后,函子本身必须再次重复所有定义才能真正实现模块类型 SummaSummarum,我得到了3个非抽象类型的

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

但是,仔细看一下,声明中有很多冗余:

  • 首先,签名和函子都必须在.mli文件中声明
  • 其次,签名必须在.ml文件中完全重复(这里是否有与.mli文件不同的法律方式?)
  • 最后,函子本身必须再次重复所有定义才能真正实现模块类型
SummaSummarum,我得到了3个非抽象类型的定义站点(例如,当我想允许模式匹配时)。这是完全荒谬的,因此我认为有一些办法。所以我的问题有两个:

  • 有没有一种方法可以在.ml文件中重复.mli文件中的模块类型,而无需手动写入?例如,模块签名的ppx_导入
  • 有没有办法在.ml文件中的模块中包含模块类型?例如,当模块类型只有一个抽象类型定义时,定义该类型并仅复制非抽象类型
    • 您已经可以将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中定义的内容。在本例中,情况并非如此,因为没有类型定义

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

    • 您的抽象由.ml指定
    • 阅读它使它变得非常清晰(因为人们从stdlib中知道这种模式)
    • 您在.mli中输入的所有内容都已在.ml中
    如果您所在的团队要求您实际给出mli,只需使用
    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部分)。我同意一些冗余避免是有用的,但在大多数情况下,它可以通过简单的解决办法来处理。