Skip to main content

Type definitions

coq-of-ocaml generates the Coq definitions corresponding to OCaml's type definitions.

Single type definitions

Synonyms

Type synonyms are transformed to Coq definitions:

type string_list = string list

generates:

Definition string_list := list string.

Records

OCaml records are transformed to Coq records, namespaced into a module to prevent name collisions. The transformation includes the with_ operators for field substitutions:

type answer = {
code : int;
message : string }

generates:

Module answer.
Record record := {
code : Z;
message : string }.
Definition with_code (r : record) code : record :=
{| code := code; message := message r |}.
Definition with_message (r : record) message : record :=
{| code := code r; message := message |}.
End answer.
Definition answer := answer.record.

Algebraic data types

Algebraic data types generate an inductive definition in Coq:

type 'a tree =
| Leaf of 'a
| Node of 'a tree * 'a tree

generates:

Inductive tree (a : Set) : Set :=
| Leaf : a -> tree a
| Node : (tree a) -> (tree a) -> tree a.

Arguments Leaf {_}.
Arguments Node {_}.

The type parameter a is set implicit for the data constructors with the command Arguments, as it is implicit in OCaml too.

For data constructors with a record parameter, the convention (taken from the OCaml compiler) is to name the corresponding record type.Constructor. For example:

type element =
| Point of { x : int; y : int}
| Rectangle of { height : int; width : int}

generates:

Module element.
Module Point.
Record record {x y : Set} := {
x : x;
y : y }.
Arguments record : clear implicits.
End Point.
Definition Point := Point.record.

Module Rectangle.
Record record {height width : Set} := {
height : height;
width : width }.
Arguments record : clear implicits.
End Rectangle.
Definition Rectangle := Rectangle.record.
End element.

Inductive element : Set :=
| Point : element.Point Z Z -> element
| Rectangle : element.Rectangle Z Z -> element.

The definitions of the constructors' records are polymorphic so that they can be applied to the type being defined if needed (in this case the type element).

Extensible types

The various forms of extensible types are ignored:

exception Too_long of string

generates:

(* exception Too_long *)

and:

type error += Lazy_script_decode

generates:

(* type_extension *)

Polymorphic variants

The polymorphic variant types are converted to the corresponding Coq inductive as an approximation:

type json =
[ `O of (string * json) list
| `Bool of bool
| `Float of float
| `A of json list
| `Null
| `String of string ]

generates:

Inductive json : Set :=
| Bool : bool -> json
| Null : json
| O : list (string * json) -> json
| Float : Z -> json
| String : string -> json
| A : list json -> json.

Mutually recursive types

With synonyms

Coq only accept mutually recursive types on inductive definitions. A known trick is to use a Coq notation to simulate mutual definitions on type synonyms:

type path = path_item list

and path_item =
| Field of string (** A field in an object. *)
| Index of int (** An index in an array. *)
| Star (** Any / every field or index. *)

generates:

Reserved Notation "'path".

Inductive path_item : Set :=
| Index : Z -> path_item
| Field : string -> path_item
| Star : path_item

where "'path" := (list path_item).

Definition path := 'path.

With records

For mutual definitions with a record, coq-of-ocaml first generate record skeletons, so that the record definitions are transformed into type synonyms:

type 'o t =
[ `Ok of 'o (* 200 *)
| `OkStream of 'o stream (* 200 *)
| `Error of error list option (* 500 *) ]

and 'a stream = {next : unit -> 'a option Lwt.t; shutdown : unit -> unit}

generates:

Reserved Notation "'stream".

Module stream.
Record record {next shutdown : Set} := {
next : next;
shutdown : shutdown }.
Arguments record : clear implicits.
Definition with_next {next_type shutdown_type : Set}
(r : record next_type shutdown_type) next
: record next_type shutdown_type :=
{| next := next; shutdown := shutdown r |}.
Definition with_shutdown {next_type shutdown_type : Set}
(r : record next_type shutdown_type) shutdown
: record next_type shutdown_type :=
{| next := next r; shutdown := shutdown |}.
End stream.
Definition stream_skeleton := stream.record.

Inductive t (o : Set) : Set :=
| OkStream : 'stream o -> t o
| Ok : o -> t o
| Error : option (list Error_monad._error) -> t o

where "'stream" := (fun (a : Set) =>
stream_skeleton (unit -> Lwt.t (option a)) (unit -> unit)).

Definition stream := 'stream.

Arguments OkStream {_}.
Arguments Ok {_}.
Arguments Error {_}.

GADTs

The type annotations on GADTs do not directly translate to Coq annotations compatible with the dependent pattern-matching of Coq. The solution adopted by coq-of-ocaml is to erase the GADT type annotations, and let the user manually add axioms to validate pattern-matching on GADT expressions.

For example:

type (_, _) comparable_struct =
| Int_key : type_annot option -> (z num, _) comparable_struct
| String_key : type_annot option -> (string, _) comparable_struct
| Bool_key : type_annot option -> (bool, _) comparable_struct
| Pair_key :
(('a, leaf) comparable_struct * field_annot option)
* (('b, _) comparable_struct * field_annot option)
* type_annot option
-> (('a, 'b) pair, comb) comparable_struct

translates to:

Reserved Notation "'comparable_struct".

Inductive comparable_struct_gadt : Set :=
| Int_key : option type_annot -> comparable_struct_gadt
| String_key : option type_annot -> comparable_struct_gadt
| Bool_key : option type_annot -> comparable_struct_gadt
| Pair_key :
comparable_struct_gadt * option field_annot ->
comparable_struct_gadt * option field_annot -> option type_annot ->
comparable_struct_gadt

where "'comparable_struct" := (fun (_ _ : Set) => comparable_struct_gadt).

Definition comparable_struct := 'comparable_struct.

The type comparable_struct_gadt is temporarily introduced as a version of comparable_struct without type parameters. Then comparable_struct is defined by ignoring its type parameters, preserving the arity of the OCaml type (here two type parameters). The use of a notation for comparable_struct allows the use of GADTs in mutually recursive types, but is not strictly necessary in this specific example.