问题
I was trying to answer this question: Ocaml selecting a type's subtype in another type declaration using phantom types. So I was about to propose this code:
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)
I was expecting the compiler to reject the code at the line
let c = g square in
because g
is of type red t -> colour
and square
is of type yellow t
. But everything compiled, and the program can be executed.
What did I miss here? Is this the expected behavior of the compiler?
回答1:
Since you're exposing the structure of CoulouredShape.t
in the signature of ColouredShape
, the type checker knows that both red t = shape * colour
and yellow t = shape * colour
, and it then follows that red t = yellow t
.
If you make ColouredShape.t
abstract however, those type equalities aren't known outside ColouredShape
, and hence you'll get the appropriate error:
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
回答2:
One solution is to make the type abstract, i.e. have the module interface expose only this:
(* abstract *)
type 'a t
instead of
(* concrete *)
type 'a t = shape * colour
An in-between solution that works with recent versions of OCaml is to declare the type as private:
type 'a t = private (shape * colour)
This is generally useful to expose the structure of a type for pattern-matching purposes, while forcing the user to create well-formed objects by calling the module's functions.
A simpler example of the use of private
is for creating a unique 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
来源:https://stackoverflow.com/questions/60642577/apparently-invalid-phantom-type-in-ocaml-accepted-by-the-compiler