Help w. my first GADT : unwrapping Sqlite3.Data.t

Bonjour les chameaux! / Hello camelers!

I would like to convert sqlite3-ocaml returns from Sqlite3.Data.t array to plain ocaml types in a tuple. I guess unwrapping the Data.t can be done using a GADT, here’s my very very first attempt:

(* simulate Sqlite3.Data.t *)

type t =
| NONE
| NULL
| INT of int64
| FLOAT of float
| TEXT of string
| BLOB of string ;;

(* a simple GADT to unwrap Sqlite3.Data.t *)

type _ dbval =
    | INT : int64 -> int64 dbval
    | FLOAT : float -> float dbval
    | TEXT : string -> string dbval
    | BLOB : string -> string dbval
    | NONE | NULL ;;

let unwrap_data : type a. a dbval -> a = fun dbval ->
    match dbval with
    | INT x -> x
    | FLOAT x -> x
    | TEXT str -> str
    | BLOB str -> str ;;

let tuple_of_array4 (arr: t array) =
    assert (Array.length arr = 4) ;
    (unwrap_data arr.(0), unwrap_data arr.(1), unwrap_data arr.(2), unwrap_data arr.(3)) ;;

Compilation fails with this typing error:

File "database.ml", line 233, characters 17-24:
233 |     (unwrap_data arr.(0), unwrap_data arr.(1), unwrap_data arr.(2), unwrap_data arr.(3)) ;;
                       ^^^^^^^
Error: This expression has type t but an expression was expected of type
         'a dbval

What am I doing wrong? I need to make type t compatible with type 'a dbval.
Thanks in advance.

2 Likes

You cannot make the type t and 'a dbval compatible, there are different types.

A very important point to keep in mind with GADTs is that one cannot create type-level information from dynamical values. In other words, there are no functions of type x : t -> f(x) dbvalthat will infer the type of its return from the value of its argument in OCaml.

Thus the type of the final result must come from your code source rather than from the dynamical data.
For instance, you can define constructor from the type t to the right dbval type:

exception Type_error

let int: t -> _ dbval = function
| INT x -> INT x
| _ -> raise Type_error

let float: t -> _ dbval = function
| FLOAT x -> FLOAT x
| _ -> raise Type_error

Then if you know the type of the tuple, you can write it as:

let tuple_of_array4 (arr: t array) =
    assert (Array.length arr = 4) ;
    int arr.(0), int arr.(1), int arr.(2), int arr.(3)

or possibly as

let int4 = int, int, int, int
let tuple (a,b,c,d) arr =
  assert (Array.length arr = 4) ;
  a arr.(0), b arr.(1), c arr.(2), d arr.(3)

There are more complex alternatives based on type witness, that allow to implement a form of static matching over the dynamical type of data, but the core idea that the types are always present in the source code in some way is the same.

1 Like

I think we need to re-examine your actual goal with this approach. The pertinent question is: why do you want to convert from Sqlite3.Data.t array to ‘plain OCaml types in a tuple’? What do you achieve with that?

Oh I didn’t noticed it would be dynamical typing! I’m too used to ppx (and previously camlp4) written db abstraction layer!

I’m simply replacing sqlexpr by plain sqlite3-ocaml in some existing code of mine. sqlexpr quick doco:

But I can live with a Data.t array!

Everybody has their favourite way of wrapping SQLite. Here’s mine (no PPX): GitHub - yawaramin/ocaml_sql_query: PoC of functional-style SQL query

It has a little data translation layer to convert from Data.t array to the desired return type.

1 Like