Ocaml 为什么可以';在实现模块类型时是否添加类型约束?

Ocaml 为什么可以';在实现模块类型时是否添加类型约束?,ocaml,type-constraints,Ocaml,Type Constraints,我试着(只是出于兴趣)这样做: module type CAT = sig type ('a, 'b) t val id : ('a, 'a) t val (@) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t end module Lst = struct type ('a, 'b) t = 'a list constraint 'a = 'b let id = [] let (@) = (@) end module L

我试着(只是出于兴趣)这样做:

module type CAT = sig
  type ('a, 'b) t
  val id : ('a, 'a) t
  val (@) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end

module Lst = struct
  type ('a, 'b) t = 'a list constraint 'a = 'b
  let id = []
  let (@) = (@)
end

module L : CAT = Lst (* (error) *)
但我得到:

   Type declarations do not match:
     type ('b, 'a) t = 'b list constraint 'a = 'b
   is not included in
     type ('a, 'b) t
为什么这不安全?所有能看到具体类型的东西也能看到约束,所以我认为你不能用错误的类型(例如,用
(string,int)t
参数调用
@


Update:对于那些说我的模块没有实现签名的人,因为它要求类型是相同的,所以考虑下面的(只列出列表变量中的列表)被接受,尽管有相同的行为:

module Lst = struct
  type ('a, 'b) t =
    List : 'a list  -> ('a, 'a) t

  let id = List []
  let (@) (type a) (type b) (type c) (a:(b, c) t) (b:(a, b) t) : (a, c) t =
    match a, b with
    | List a, List b -> List (a @ b)
end

在我看来,您的
Lst
模块的类型不是
CAT
CAT
允许两种类型的
'a
'b
相互独立。
Lst
模块要求它们相同。如果
L
模块是
CAT
类型,那么它应该允许我制作
(string,int)t
类型的东西,但它不是


至少对我来说,错误消息有点令人困惑。

类型签名
CAT
Lst
模块的类型更一般。您还需要将类型约束放在抽象类型上,即
type('a,'b)t constraint'a='b

这给了我们以下信息:

module type CAT = sig
  type ('a, 'b) t constraint 'a = 'b
  val id : ('a, 'a) t
  val (@) : ('b, 'c) t -> ('a, 'b) t -> ('a, 'c) t
end
toplevel按如下方式打印,在
(@)
的签名中显示单个类型变量:

“类型x不包括在类型y中”形式的错误消息指的是类型或模块类型,作为可能值集的规范,因此使用术语“包括”

对于模块实现(
Lst
),我们有一个模块类型。仅当签名与模块原始签名相同(相等集)或更专业(严格子集)时,才允许将签名(模块类型
CAT
)应用于模块

可以编写
模块X:sig val f:unit->unit end=struct let f X=X end
但不是
模块X:sig val f:'a->'a end=struct let f()=()end
。后者给出了以下错误:

Error: Signature mismatch:
       Modules do not match:
       sig val f : unit -> unit end                                           
       is not included in
         sig val f : 'a -> 'a end
       Values do not match:
         val f : unit -> unit
       is not included in
         val f : 'a -> 'a

这与在某些表达式上放置类型约束不同,在这种情况下,约束是要应用的掩码(与之相交的集),而不是子集。例如,可以编写
让f:unit->'a=fun x->x
,即使
f
的签名最终是
unit->unit
,这是
unit->'a

的一个严格子集或子类型

module type S =
sig
  type ('a, 'b) t
end

module M =
struct
  type ('a, 'b) t = 'a list constraint 'a = 'b
end
正如杰弗里已经指出的,
M
不是
S
类型,因为它允许
t
的应用更少:根据签名
S
,类型
(int,string)t
将是完全合法的(格式良好),但
M
不允许这种类型(
(int,string)M.t
不是合法类型,因为它违反了显式约束)


所有这些都完全独立于类型是否实际有人居住的问题,即是否可以构造该类型的值。在第二个示例中,模块使相应的类型格式良好,尽管它是无人居住的。但是,无人居住类型是合法的,有时甚至是有用的(请参见幻影类型的概念)。

我不明白为什么签名意味着您应该能够创建任意类型。例如,如果签名定义了
type'a t
,模块定义了
type'a t=Int:Int->Int t
,那么OCaml很高兴,即使您不能生成
string t
@ThomasLeonard,这是错误的,您也可以生成
string t
。该类型中没有任何值,但这并不意味着它是无效类型。杰弗里的回答恰到好处。那么你会用什么代码“制作一个
字符串t
”?(例如,您可能会使用
[“foo”]
“创建
字符串列表”
)。如果一个类型是无人居住的,那么根据定义,您不能生成该类型的值。请注意,我在问题中添加了另一个示例,它的类型和行为与我的代码相同,并且被OCaml接受。@ThomasLeonard,我以为你的意思是你不能形成类型
字符串t
。但是你可以。您不能构造值,但这不会使其成为格式错误的类型。类似地,第二个示例与第一个示例的“行为”不同:在第二个示例中,
(int,string)t
是合法类型,而第一个示例则不是。不管它是否有人居住。类型良好形式性和居住性是两个独立的属性(有时无居住类型很有用,参见幻影类型)。我的意思是“制造任意类型的值”,我想这就是Jeffrey的意思。你对“合法”类型的区分听起来很重要。是否要添加单独的答案?看一个例子,说明接受我的第一个示例的编译器如何在运行时导致错误行为,这将非常有用。但在您的示例中,您的模块确实无法处理例如
f3
,因此错误是正确的。但是我的
@
可以处理所有可能的
('a,'b)Lst.t
。我为这个问题添加了一个进一步的示例,表明没有必要限制
CAT
类型来创建具有相同行为的
Lst
模块(尽管实现效率较低)。每种可能的
('a,'b)Lst.t
实际上都是每种可能的
('a,'a)由于类型约束,Lst.t
“a=”b。试试这个:
type('a,'b)t=unitconstraint'a='b
,然后
type t'=(int,string)t
。您会得到错误“此类型字符串应该是int类型的实例”。请参阅Jeffrey回答下的我的评论,了解第二个示例与第一个示例的行为不同的原因。简言之,你似乎很困惑
module type S =
sig
  type ('a, 'b) t
end

module M =
struct
  type ('a, 'b) t = 'a list constraint 'a = 'b
end