OCaml中的朋友模块

时间:2011-02-23 08:29:25

标签: module dependencies ocaml friend

我目前有两个“层”模块,代表数据库中的标识符 - 数据关系。

第一层定义标识符类型,例如IdUser.tIdPost.t,而第二层定义数据类型,例如User.tPost.t。我需要在第二层的模块之前编译第一层的所有模块,因为Post.t必须保留其作者的IdUser.tUser.t保持{{1}他访问的最后五个帖子。

目前,IdPost.t提供的功能只能由IdUser.t使用,例如将User.t转换为IdUser.t的功能:出于安全原因,此转换只能由函数IdUser.current执行。由于User.check_passwordIdUser是独立的模块,我需要将这些功能定义为公共函数,并依赖约定来避免在User之外的任何地方调用它们,这很脏。对称情况发生在User

IdPost.mine

有没有办法在module IdUser : sig type t type current val current_of_t : t -> current (* <--- Should not be public! *) end = struct type t = string type current = string let current_of_t x = x end module IdPost : sig type t type mine val mine_of_t : t -> mine (* <--- Should not be public! *) end = struct type t = string type mine = string let mine_of_t x = x end module Post : sig (* Should not "see" IdUser.current_of_t but needs IdPost.mine_of_t *) val is_mine : IdUser.current -> IdPost.t -> IdPost.mine end module User : sig (* Should not "see" IdPost.mine_of_t but needs IdUser.current_of_t *) val check_password : IdUser.t -> password:string -> IdUser.current end 中定义只能在模块current_of_t : t -> current内调用的IdUser函数?

编辑:这是一个模块对的简化示例,但对于一对无法推广到多对的明显解决方案我需要为多对解决这个问题 - 大约18对,实际上......所以,我把它扩展为两个对的例子。

4 个答案:

答案 0 :(得分:3)

所以IdUser实际上是一种存在类型:对于User,存在一种类型 IdUser.current这样公众IdUser.t就可以解除它。有几种方法可以编码:functorize User如Gasche所示,如果静态地管理依赖是足够的,或者如果你需要更多的动力,则使用一流的模块或对象。

为了方便起见,我将使用private type abbreviations来解决Gasche的例子,并展示如何利用半透明来避免过多地实现类型的私有化。首先,这可能是一个限制,我想声明一个持久IDs的ADT:

(* File id.ml *)
module type ID = sig
  type t
  type current = private t
end

module type PERSISTENT_ID = sig
  include ID
  val persist : t -> current
end

有了这个,我可以使用Post的具体类型定义ID的类型,但使用ADT来强制执行与持久性相关的业务规则:

(* File post.ml *)
module Post
  (UID : ID with type t = string)
  (PID : PERSISTENT_ID with type t = int)
: sig 
  val is_mine : UID.current -> PID.t -> PID.current
end = struct
  let is_mine uid pid =
    if (uid : UID.current :> UID.t) = "me" && pid = 0
      then PID.persist pid
      else failwith "is_mine"
end

User s相同:

(* File user.ml *)
module User
  (UID : PERSISTENT_ID with type t = string)
: sig
  val check_password : UID.t -> password:string -> UID.current
end = struct
  let check_password uid ~password =
    if uid = "scott" && password = "tiger"
      then UID.persist uid
      else failwith "check_password"
end

请注意,在这两种情况下,我都使用具体但私有的ID类型。将所有内容绑定在一起很简单,只需使用持久性规则来定义ID ADT:

module IdUser = struct 
  type t = string
  type current = string
  let persist x = x
end

module IdPost = struct 
  type t = int
  type current = int
  let persist x = x
end

module MyUser = User (IdUser)
module MyPost = Post (IdUser) (IdPost)

此时并且为了完全解耦依赖关系,您可能需要可以从此模块导出的USERPOST签名,但是添加它们很简单。

答案 1 :(得分:2)

至少在简化示例中似乎有效的一种方法是将IdUserUser分组到同一模块中:

module UserAndFriends : sig ... end = struct
 module IdUser : sig
  ...
 end = struct
  ...
 end

 module User = struct
   ...
 end
end

module Post : sig 
  val create : (* <--- Should not "see" IdUser.current_of_t *)
    author:IdUser.current -> title:string -> body:string -> IdPost.t
end

隐藏UserAndFriends签名中的危险功能可以得到您想要的结果。如果您不想制作包含IdUserUser的大文件,则可以使用ocamlc的-pack选项创建UserAndFriends。请注意,在这种情况下,您必须仔细制作Makefile,以便在编译IdUser时看不到UserPost的.cmi文件。我不是Frama-C的Makefile专家,但我认为我们使用单独的目录并仔细定位编译器选项-I

答案 2 :(得分:2)

我建议你通过Post模块的签名来参数化User(以及可能IdUser的一致性):你会使用current_of_t的签名来{{1 }},还有一个没有User

这保证Post不使用Post私有功能,但IdUser的公共接口仍然过于宽松。但是使用此设置,您已经颠倒了依赖关系,IdUser(敏感部分)可以直接控制其使用,将自己(使用私有部分)提供给IdUser并将公共签名限制为公共部分。

IdUser

编辑:Pascal Cuoq的并发 - 在时间意义上 - 解决方案非常好。实际上它更简单,并且具有更少的样板。我的解决方案添加了一个抽象,允许稍微更加模块化,因为您可以独立于module type PrivateIdUser = sig val secret : unit end module type PublicIdUser = sig end module type UserSig = sig (* ... *) end module MakeUser (IdUser : PrivateIdUser) : UserSig = struct (* ... *) end module IdUser : sig include PublicIdUser module User : UserSig end = struct module IdUser = struct let secret = () end module User = MakeUser(IdUser) include IdUser end module Post = struct (* ... *) end 定义User

我认为哪种解决方案最好可能取决于具体应用。如果您有许多使用IdUser私人信息的不同模块,那么使用仿函数单独编写它们而不是将每个人捆绑在同一模块中可能是个好主意。如果只有PrivateIdUser需要在“私有区域”并且它不是很大,那么Pascal的解决方案是更好的选择。

最后,虽然强制显式UserPrivate接口可以被视为额外的负担,但它也是一种使用不同模块的访问属性更明确的方法模块层次结构。

答案 3 :(得分:0)

通过递归模块,一流模块和GADT的组合,可以实现对签名的细粒度控制,但是限制是所有模块应该在同一顶层模块内并且首先拆包 - 递归模块中的类模块应该在每个函数中单独完成(而不是在模块级别,因为它会导致运行时异常Undefined_recursive_module):

module rec M1 : sig
  module type M2's_sig = sig
    val a : int
    val c : float
  end

  module type M3's_sig = sig
    val b : string
    val c : float
  end

  type _ accessor =
    | I'm_M2 : M2.wit -> (module M2's_sig) accessor
    | I'm_M3 : M3.wit -> (module M3's_sig) accessor

  val access : 'a accessor -> 'a

  type wit

  val do_it : unit -> unit
end = struct
  module type M2's_sig = sig
    val a : int
    val c : float
  end

  module type M3's_sig = sig
    val b : string
    val c : float
  end

  type _ accessor =
    | I'm_M2 : M2.wit -> (module M2's_sig) accessor
    | I'm_M3 : M3.wit -> (module M3's_sig) accessor

  module M1 = struct
    let a = 1
    let b = "1"
    let c = 1.
  end

  let access : type a. a accessor -> a =
    function
    | I'm_M2 _ -> (module M1)
    | I'm_M3 _ -> (module M1)

  type wit = W

  let do_it () =
    let (module M2) = M2.(access @@ I'm_M1 W) in
    let (module M3) = M3.(access @@ I'm_M1 W) in
      Printf.printf "M1: M2: %d %s M3: %d %s\n" M2.a M2.b M3.a M3.b
end
and M2 : sig
  module type M1's_sig = sig
    val a : int
    val b : string
  end

  module type M3's_sig = sig
    val b : string
    val c : float
  end

  type _ accessor =
    | I'm_M1 : M1.wit -> (module M1's_sig) accessor
    | I'm_M3 : M3.wit -> (module M3's_sig) accessor

  val access : 'a accessor -> 'a

  type wit

  val do_it : unit -> unit
end = struct
  module type M1's_sig = sig
    val a : int
    val b : string
  end

  module type M3's_sig = sig
    val b : string
    val c : float
  end

  type _ accessor =
    | I'm_M1 : M1.wit -> (module M1's_sig) accessor
    | I'm_M3 : M3.wit -> (module M3's_sig) accessor

  module M2 = struct
    let a = 2
    let b = "2"
    let c = 2.
  end

  let access : type a. a accessor -> a =
    function
    | I'm_M1 _ -> (module M2)
    | I'm_M3 _ -> (module M2)

  type wit = W

  let do_it () =
   let (module M1) = M1.(access @@ I'm_M2 W) in
   let (module M3) = M3.(access @@ I'm_M2 W) in
   Printf.printf "M2: M1: %d %f M3: %d %f\n" M1.a M1.c M3.a M3.c
end
and M3 : sig
  module type M1's_sig = sig
    val a : int
    val b : string
  end

  module type M2's_sig = sig
    val a : int
    val c : float
  end

  type _ accessor =
    | I'm_M1 : M1.wit -> (module M1's_sig) accessor
    | I'm_M2 : M2.wit -> (module M2's_sig) accessor

  val access : 'a accessor -> 'a

  type wit

  val do_it : unit -> unit
end = struct
  module type M1's_sig = sig
    val a : int
    val b : string
  end

  module type M2's_sig = sig
    val a : int
    val c : float
  end

  type _ accessor =
    | I'm_M1 : M1.wit -> (module M1's_sig) accessor
    | I'm_M2 : M2.wit -> (module M2's_sig) accessor

  module M3 = struct
    let a = 3
    let b = "3"
    let c = 3.
  end

  let access : type a. a accessor -> a =
    function
    | I'm_M1 _ -> (module M3)
    | I'm_M2 _ -> (module M3)

  type wit = W

  let do_it () =
    let (module M1) = M1.(access @@ I'm_M3 W) in
    let (module M2) = M2.(access @@ I'm_M3 W) in
    Printf.printf "M3: M1: %s %f M2: %s %f\n" M1.b M1.c M2.b M2.c
end

let () =
  M1.do_it ();
  M2.do_it ();
  M3.do_it ()