7

I was playing around with GADT and phantom types in OCaml. I understood that GADT is a convenience for describing certain kinds of phantom types—correct me if I'm wrong. So I decided to try out to convert a program using GADT type into an one with ADT and phantom types.

I took an GADT program from this blog post as a starting point. It's a small bool/int expression evaluator, here's the gist of it:

module GADT = struct
  type _ value =
    | Bool : bool -> bool value
    | Int : int -> int value

  type _ expr =
    | Value : 'a value -> 'a expr
    | If : bool expr * 'a expr * 'a expr -> 'a expr
    | Eq : 'a expr * 'a expr -> bool expr
    | Lt : int expr * int expr -> bool expr

  let rec eval : type a. a expr -> a = function
    | Value (Bool b) -> b
    | Value (Int i) -> i
    | If (b, l, r) -> if eval b then eval l else eval r
    | Eq (a, b) -> eval a = eval b
    | Lt (a,b) -> a < b
end

So I started converting it into ADT + phantom types as follows:

module ADT = struct
  type 'a value =
    | Bool of bool
    | Int of int

  let _Bool b : bool value = Bool b
  let _Int  i :  int value = Int i

  type 'a expr =
    | Value of 'a value
    | If of bool expr * 'a expr * 'a expr
    | Eq of 'a expr * 'a expr
    | Lt of int expr * int expr

  let _Value v : 'a expr = Value v
  let _If (cond, cons, alt) : 'a expr = If (cond, cons, alt)
  let _Eq (left, right) : bool expr = Eq (left, right)
  let _Lt (left, right) : bool expr = Lt (left, right)

  let rec eval : type a. a expr -> a = function
    | Value (Bool b) -> b
    | Value (Int  i) -> i
    | If (cond, cons, alt) -> if eval cond then eval cons else eval alt
    | Eq (left, right) -> eval left = eval right
    | Lt (left, right) -> left < right
end

You don't have control over type variable of ADT constructors, so I created my own _Bool, _Int, etc. constructors to force the necessary type.

However, my ADT module does not compile:

  let rec eval : type a. a expr -> a = function
    | Value (Bool b) -> b
                        ^
Error: This expression has type bool but an expression was expected of type a 

At this point I assume that my idea is flawed and you can't convert GADT to ADT in this way. However I would like to hear someone more experienced on this topic.

Vladimir Keleshev
  • 13,753
  • 17
  • 64
  • 93

1 Answers1

6

You can't do this without GADT, as phantom type cannot serve as a witness for a compiler, that an expression has specific type, since with phantom type you actually can do the following:

let bool b : int value = Bool b;;
val bool : bool -> int value = <fun>

That's why having a phantom type is not sufficient, and that's why the GADT was introduced in OCaml.

ivg
  • 34,431
  • 2
  • 35
  • 63
  • You say "phantom type cannot serve as a witness for a compiler" but wouldn't GADT type in the following case be a lying witness? `type _ value = Bool : bool -> int value` – Vladimir Keleshev May 03 '15 at 19:44
  • 1
    Nope, because compiler will not allow you to create wrong instance, e.g., `let bool x : int value = Bool x` will result in an error. – ivg May 03 '15 at 19:51
  • In case of GADT, right? I see. To put it in another way: even though for `ADT` module I created constructors like `let _Bool b : bool value = Bool b` which constraint the return type to `bool value` the original constructor `Bool` still returns `'a value` and compiler needs to take that into account, so it *cannot* be sure that in `| Value (Bool b) -> b` branch `Bool b` is `bool value` and not `'a value`, so it must consider the case where `val b : int value = Bool true` for which `| Value (Bool (b : int)) -> (b : bool)` branch does not type-check. Did I get this right? – Vladimir Keleshev May 03 '15 at 20:08