编译器接受的 OCaml 中显然无效的幻影类型
Apparently invalid phantom type in OCaml accepted by the compiler
我试图回答这个问题:
使用幻像类型。所以我正要提出这个代码:
type colour = Red | Blue | Yellow
type shape = Rectangle | Square
module ColouredShape : sig
(* Type parameterized by 'a, just for the type system. 'a does not appear in the
right hand side *)
type 'a t = shape * colour
(* Dummy types, used as labels in the phantom type *)
type red
type yellow
val make_red : shape -> red t
val make_yellow : shape -> yellow t
val make_rectangle : unit -> red t
val make_square : unit -> yellow t
val f : 'a t -> colour
val g : red t -> colour
val h : yellow t -> colour
end
=
struct
type 'a t = shape * colour
type red
type yellow
let make_red s = (s, Red)
let make_yellow s = (s, Yellow)
let make_rectangle () = make_red Rectangle
let make_square () = make_yellow Square
let f x = snd x
let g x = snd x
let h x = snd x
end
open ColouredShape
open Printf
let _ =
let rectangle = make_rectangle () in
let square = make_square () in
let c = f square in
printf "%b\n" (c = Red);
let c = f rectangle in
printf "%b\n" (c = Red);
let c = g square in
printf "%b\n" (c = Red);
let c = g rectangle in
printf "%b\n" (c = Red);
let c = h square in
printf "%b\n" (c = Red);
let c = h rectangle in
printf "%b\n" (c = Red)
我原以为编译器会拒绝
行的代码
let c = g square in
因为g
是red t -> colour
类型,square
是yellow t
类型。但是一切都编译好了,程序可以执行了。
我错过了什么?这是编译器的预期行为吗?
由于您在 ColouredShape
的签名中暴露了 CoulouredShape.t
的结构,类型检查器知道 red t = shape * colour
和 yellow t = shape * colour
,然后它会遵循red t = yellow t
.
但是,如果您将 ColouredShape.t
抽象化,这些类型等式在 ColouredShape
之外是未知的,因此您会得到相应的错误:
let c = g square
^^^^^^
Error: This expression has type ColouredShape.yellow ColouredShape.t
but an expression was expected of type
ColouredShape.red ColouredShape.t
Type ColouredShape.yellow is not compatible with type
ColouredShape.red
一个解决方案是使类型抽象化,即让模块接口只公开这个:
(* abstract *)
type 'a t
而不是
(* concrete *)
type 'a t = shape * colour
适用于最新版本 OCaml 的中间解决方案是将类型声明为私有:
type 'a t = private (shape * colour)
这通常可用于公开类型的结构以进行模式匹配,同时强制用户通过调用模块的函数来创建格式正确的对象。
使用 private
的一个更简单的示例是创建唯一 ID:
module ID : sig
type t = private int
val create : unit -> t
end = struct
type t = int (* note: no 'private' *)
let counter = ref 0
let create () =
let res = !counter in
if res < 0 then
failwith "ID.create: int overflow";
incr counter;
res
end
我试图回答这个问题:
type colour = Red | Blue | Yellow
type shape = Rectangle | Square
module ColouredShape : sig
(* Type parameterized by 'a, just for the type system. 'a does not appear in the
right hand side *)
type 'a t = shape * colour
(* Dummy types, used as labels in the phantom type *)
type red
type yellow
val make_red : shape -> red t
val make_yellow : shape -> yellow t
val make_rectangle : unit -> red t
val make_square : unit -> yellow t
val f : 'a t -> colour
val g : red t -> colour
val h : yellow t -> colour
end
=
struct
type 'a t = shape * colour
type red
type yellow
let make_red s = (s, Red)
let make_yellow s = (s, Yellow)
let make_rectangle () = make_red Rectangle
let make_square () = make_yellow Square
let f x = snd x
let g x = snd x
let h x = snd x
end
open ColouredShape
open Printf
let _ =
let rectangle = make_rectangle () in
let square = make_square () in
let c = f square in
printf "%b\n" (c = Red);
let c = f rectangle in
printf "%b\n" (c = Red);
let c = g square in
printf "%b\n" (c = Red);
let c = g rectangle in
printf "%b\n" (c = Red);
let c = h square in
printf "%b\n" (c = Red);
let c = h rectangle in
printf "%b\n" (c = Red)
我原以为编译器会拒绝
行的代码let c = g square in
因为g
是red t -> colour
类型,square
是yellow t
类型。但是一切都编译好了,程序可以执行了。
我错过了什么?这是编译器的预期行为吗?
由于您在 ColouredShape
的签名中暴露了 CoulouredShape.t
的结构,类型检查器知道 red t = shape * colour
和 yellow t = shape * colour
,然后它会遵循red t = yellow t
.
但是,如果您将 ColouredShape.t
抽象化,这些类型等式在 ColouredShape
之外是未知的,因此您会得到相应的错误:
let c = g square
^^^^^^
Error: This expression has type ColouredShape.yellow ColouredShape.t
but an expression was expected of type
ColouredShape.red ColouredShape.t
Type ColouredShape.yellow is not compatible with type
ColouredShape.red
一个解决方案是使类型抽象化,即让模块接口只公开这个:
(* abstract *)
type 'a t
而不是
(* concrete *)
type 'a t = shape * colour
适用于最新版本 OCaml 的中间解决方案是将类型声明为私有:
type 'a t = private (shape * colour)
这通常可用于公开类型的结构以进行模式匹配,同时强制用户通过调用模块的函数来创建格式正确的对象。
使用 private
的一个更简单的示例是创建唯一 ID:
module ID : sig
type t = private int
val create : unit -> t
end = struct
type t = int (* note: no 'private' *)
let counter = ref 0
let create () =
let res = !counter in
if res < 0 then
failwith "ID.create: int overflow";
incr counter;
res
end