我目前有两个“层”模块,代表数据库中的标识符 - 数据关系。
第一层定义标识符类型,例如IdUser.t
或IdPost.t
,而第二层定义数据类型,例如User.t
或Post.t
。我需要在第二层的模块之前编译第一层的所有模块,因为Post.t
必须保留其作者的IdUser.t
而User.t
保持{{1}他访问的最后五个帖子。
目前,IdPost.t
提供的功能只能由IdUser.t
使用,例如将User.t
转换为IdUser.t
的功能:出于安全原因,此转换只能由函数IdUser.current
执行。由于User.check_password
和IdUser
是独立的模块,我需要将这些功能定义为公共函数,并依赖约定来避免在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对,实际上......所以,我把它扩展为两个对的例子。
答案 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)
此时并且为了完全解耦依赖关系,您可能需要可以从此模块导出的USER
和POST
签名,但是添加它们很简单。
答案 1 :(得分:2)
至少在简化示例中似乎有效的一种方法是将IdUser
和User
分组到同一模块中:
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
签名中的危险功能可以得到您想要的结果。如果您不想制作包含IdUser
和User
的大文件,则可以使用ocamlc的-pack
选项创建UserAndFriends
。请注意,在这种情况下,您必须仔细制作Makefile,以便在编译IdUser
时看不到User
和Post
的.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的解决方案是更好的选择。
最后,虽然强制显式User
和Private
接口可以被视为额外的负担,但它也是一种使用不同模块的访问属性更明确的方法模块层次结构。
答案 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 ()