OCaml:组合递归模块和私有类型缩写时出现类型错误

时间:2017-06-30 08:58:37

标签: ocaml

我目前正在使用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中,两种类型tA.t应该是相等的。根据我的理解,发生的事情是A内部,类型t被认为是int list的同义词,而在A之外,签名告诉我们它是私有的,所以它是只是这种类型的副本,具有强制A.t :> int list。整个观点是没有相反的强制,这正是我想使用私人类型缩写的原因

但在我的情况下,我在模块A中,所以我想使用这些额外信息来说明我的类型t应该强制A.t

有没有人能更好地解释为什么会出现这种错误,以及如何避免错误? (我已经考虑过切换到抽象类型,但我得到完全相同的错误)

4 个答案:

答案 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 。拥有私有类型使其无法实现。

据我所知,您有三种选择:

  1. 删除私有
  2. 添加一个浏览,getter函数,允许B模块访问 A.t
  3. 类型值的内容
  4. 我这样做的方式:将检查功能放入模块 A

答案 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。它与声明抽象类型相同,但有以下两个例外:

  1. 编译器将类型t视为一种实现类型,它为优化打开了一条途径 - 但是,这并不意味着类型检查器认为它们是相同的类型检查员他们不同

  2. typechecker允许强制u类型键入t

  3. 因此,从类型检查器的角度来看,这两种类型是截然不同的。与OCaml类型规则一样,所有强制都应该是明确的,除非被强制,否则子类型不等于超类型。在您的情况下,由于A.t = private int listint list是不同类型,因此类型检查器会尝试将类型A.t与类型int list统一起来,但它会被拒绝。但是,允许A.t强制int list(但不是反之)。

      

    在模块A本身内写下secure_cons i(x:A.t)可能看起来很愚蠢

    你不需要写它(至少在你的简单例子中)。只需使用x :> t即可。