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 Map
s & Set
s 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.