我目前正在使用OCaml,我想创建一些以某种方式保护的类型,在某种意义上我只想选择满足某些属性的那些实例。
我发现实现这种方式的方法是将我的类型封装在一个模块中,使其成为私有,并以这样的方式定义构造函数,即检查它们正在尝试创建的对象是否满足这些属性。由于我的代码有点长,我想分成不同的模块,但我的类型是相互递归的,所以我使用递归模块。我最终处于以下情况(我简化了很多,以便它变得可读)
module rec A
: sig
type t = private int list
val secured_cons : int -> t -> t
end
= struct
type t = int list
let cons (i:int) (x:t) : t = i::x
let secured_cons i x : t = B.checker i x; cons i x
end
and B
: sig
val checker : int -> A.t -> unit
end
= struct
let checker i x = ()
end
但此代码被拒绝,并显示以下错误消息:
Characters 226-227:
let secured_cons i x = B.checker i x; cons i x
^
Error: This expression has type A.t but an expression was expected of type
t = int list
这在我看来非常奇怪,因为我们在上下文A
中,两种类型t
和A.t
应该是相等的。根据我的理解,发生的事情是A
内部,类型t被认为是int list
的同义词,而在A
之外,签名告诉我们它是私有的,所以它是只是这种类型的副本,具有强制A.t :> int list
。整个观点是没有相反的强制,这正是我想使用私人类型缩写的原因
但在我的情况下,我在模块A
中,所以我想使用这些额外信息来说明我的类型t
应该强制A.t
有没有人能更好地解释为什么会出现这种错误,以及如何避免错误? (我已经考虑过切换到抽象类型,但我得到完全相同的错误)
答案 0 :(得分:2)
我找到了一种解决此问题的方法,我将其发布在此处以防万一其他人遇到过这种情况。
我们只需要明确指出我们对系统的期望类型和强制 - 这是我的示例稍作修改的正确方式:
module rec A
: sig
type t = private int list
val secured_cons : int -> t -> t
end
= struct
type t = int list
let cons (i:int) (x:t) : t = i::x
let secured_cons i (x:A.t) = B.checker i x; cons i (x :> t)
end
and B
: sig
val checker : int -> A.t -> unit
end
= struct
let checker i x = ()
end
在模块let secured_cons i (x:A.t)
内部编写A
可能看起来很愚蠢,但据我所知,这是向系统指定它应该离开模块的唯一方法检查签名,并使用与签名相同的类型(这里是私有类型)而不是内部类型t
,它仍然是int list
的同义词
我有更棘手的案例,但这个想法可以适应他们每个人,并帮助我解决所有问题。
我仍然不完全确定发生了什么,如果有人有更明确的解释,我会非常感激
答案 1 :(得分:0)
您发生错误是因为调用B.checker
时,由于 B 的签名, x 被视为 At >。
如果您明确键入 secured_cons 函数,则可以轻松查看:
let secured_cons i (x:t) : t = B.checker i x; cons i x
现在产生对称错误:
let secured_cons i (x:t) = B.checker i x; cons i x
^
Error: This expression has type t = int list
but an expression was expected of type A.t
事实上,我认为这里有一个真正的设计问题。如果您希望模块 B 检查模块 A 生成的值,那么毫无意外B必须以某种方式检查类型 A.t 。拥有私有类型使其无法实现。
据我所知,您有三种选择:
答案 2 :(得分:0)
我很高兴听到更多有经验的用户对此有何评论,但这是我的看法。
作为开发人员,我通常会对代码的语义给予很多重视。在您的情况下,B
模块专门使用A
模块,并且没有其他目标。
因此,坚持嵌套模块(即使它使你的代码更长一点)将是我所关注的方式。揭露B
模块是没有意义的。下面是重构的例子来说明。
module A : sig
type t
val secured_cons : int -> t -> t
end = struct
type t = int list
module B : sig
val checker : int -> t -> unit
end = struct
let checker i x = ()
end
let cons i x = i::x
let secured_cons i x = B.checker i x; cons i x
end
以下是utop
:
module A : sig type t val secured_cons : int -> t -> t end
这在我看来是完美的,因为它只显示模块的接口,而不是它的实现。
作为旁注,如果您想公开B
模块的签名(例如,将其提供给仿函数),只需将其移动到A
模块的签名即可,如下:
module A : sig
type t
val secured_cons : int -> t -> t
module B : sig
val checker : int -> t -> unit
end
end = struct
type t = int list
module B = struct
let checker i x = ()
end
let cons i x = i::x
let secured_cons i x = B.checker i x; cons i x
end;;
以下是utop
:
module A :
sig
type t
val secured_cons : int -> t -> t
module B : sig val checker : int -> t -> unit end
end
答案 3 :(得分:0)
我仍然不完全确定发生了什么,如果有人有更明确的解释,我会非常感激
type u = private t
形式的私有类型缩写声明与实现类型u
distinct 的类型t
。它与声明抽象类型相同,但有以下两个例外:
编译器将类型t
视为一种实现类型,它为优化打开了一条途径 - 但是,这并不意味着类型检查器认为它们是相同的类型检查员他们不同。
typechecker允许强制u
类型键入t
。
因此,从类型检查器的角度来看,这两种类型是截然不同的。与OCaml类型规则一样,所有强制都应该是明确的,除非被强制,否则子类型不等于超类型。在您的情况下,由于A.t = private int list
和int list
是不同类型,因此类型检查器会尝试将类型A.t
与类型int list
统一起来,但它会被拒绝。但是,允许A.t
强制int list
(但不是反之)。
在模块A本身内写下secure_cons i(x:A.t)可能看起来很愚蠢
你不需要写它(至少在你的简单例子中)。只需使用x :> t
即可。