Apparently invalid phantom type in OCaml accepted by the compiler

跟風遠走 提交于 2020-03-23 12:01:23

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!