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