Packing GADT constructors in iterable data structure


#1

I need to pack GADT constructors into some data structure that will allow me to search for specific item and iterate over all items.

Here’s the simplified version which results in compile time error:

type t(_) =
  | S: t(string)
  | I: t(int);

let items: type a. list((t(a), a => bool)) = [
  (S, x => x == ""),
  (I, x => x == 0),
];

Error:

This has type:
  string t
But somewhere wanted:
  a t

The incompatible parts:
  string
  vs
  a

Any suggestions how to solve it?


#2

What’s the more complex version? It’s hard to tell what your goal is here. An even more simplified version of that code which doesn’t work is:

type t(_) =
  | S: t(string)
  | I: t(int);

let items = [ S, I ];

which makes me think you’re up the wrong tree with GADTs


#3

I’m attacking old problem w/ my form lib.

API roughly looks like this:

type FormConfig = {
  type field = | Email | Password;
  type value = string;
  let validators = [
    {
      field: Email,
      validate: value => ...
    }
  ];
};

module Form = Formality.Make(FormConfig);

<Form />

It works perfectly fine until I need a value to be something else rather than string. Current solution is:

type FormConfig = {
  type field = | Email | Password | RememberMe;
  type value = | String(string) | Bool(bool);
  let validators = [
    {
      field: Email,
      validate: value => ...
    }
  ];
};

In this case it’s possible to implement the form but field constructors are not constrained to certain value types and it results in the following inconveniences. Since I use field constructor as id to identify field related data internally (e.g. I can search for specific validator in validators list, also there’re a number of Maps & Sets where t = field). For these reasons, I can’t pack value type into field constructors, e.g. Email(string) | Password(string).

When I started investigating possible solutions GADTs seemed what I need:

type field(_) =
 | Email: field(string)
 | Password: field(string)
 | RememberMe: field(bool);

And I was able to implement types, getters, setters etc using this, e.g.:

type FormConfig = {
  type field(_) =
    | Email: field(string)
    | Password: field(string)
    | RememberMe: field(bool);

    type state = {
      email: string,
      password: string,
      rememberMe: bool,
    };

  let get: type value. (state, field(value)) => value =
    (state, field) =>
      switch (field) {
      | Email => state.email
      | Password => state.password
      | RememberMe => state.rememberMe
      };
};

But then I faced issue that I can’t use field type in iterables data structures, like list or Belt.Map.

From what I read, people use existential wrappers to store non-heterogeneous data in such structures but such wrapper completely hides underlying types and there’s no way to unpack it:

type packedField = | Field(field('value)): packedField;
let unpack = fun | Field(x) => x; /* nope, type escapes the scope */

Basically, w/ existential types I can create list of GADT constructors but there’s no way to use its content later on (or I don’t understand how to do it). And considering it, I’m curious what are the use cases for existential wrappers in general.


#4

Solved this problem by re-designing public API. OCaml teaches how to properly (:crossed_fingers:) define boundaries of abstraction the hard way lol.


#5

Follow the discuss here : https://discuss.ocaml.org/t/packing-gadt-constructors-in-iterable-data-structure/2678/6