typegist design notes

Representation

The type representation is similar to Balestrieri et al.'s low level view (§2.3.4). However the representation is not extensible, a few case are dropped, some representations are unified, some specializations are added.

Here are a few salient points:

Metadata

The representation is decorated with type-indexed existential metadata. This allows generic functions to be customized. See for example the keys in Typegist.Fun.Generic. We use an ad-hoc type level defunctionalization of Yallop et al. for two parameters to allow metadata dictionaries to be polymorphic on the type of represented types and fields (the reason why we have to parameters: one for the product type and another for the field type). Two remarks:

Regarding metadata placement, we went through a couple of designs before settling on this one. But those were rather the result of a confused mind about what type gists were really:

  1. Have the metadata everywhere in the nodes and the (leaves of the) leaves of the representation. This is painful for processors which have to do the lookups in every leaves. It also increases ambiguities for users and processors as to where metadata should be specified; there's too many places where it can be specified.
  2. A single case Meta of 'a Meta.t * 'a t in the toplevel expression variant (it seems that's what LexiFi does). This makes less lookups but it means that gist processors need to thread and maintain a metadata context and make sure they don't apply to the wrong gist.
  3. Using a classical AST approach where you have a type for type expressions and a type for an expression decorated with metadata.

In the end we went with 3. since this allows us to also add a couple of things we ended up needing like a Type.Id on every expression node to allow to identify them for Typegist.Type.Gist.rebinding and all sorts of other munging operations you may be interested in doing to get coherent gists.

Compiler or metaprogramming ideas

Assuming you are devising type gists by hand or macros or mixture thereof, a few meta protractions could be useful to have. If we have source-level macros then you could streamline gist definitions while retaining their flexibility if you need to. Most type gist definitional patterns can be seen the cookbook blueprints.

  1. DRY naming protractions. Stuff to access the names you define as strings in the representation so that there is a single source of truth rooted in the OCaml type definition. That's mostly constructor names, record field names, type names and module names (__MODULE__ is rather __COMPILATION_UNIT__ and given the terrible mess dune does to source files it would likely not be that useful).
  2. Product constructors. Given a tuple, record or variant case be able to reify a function that constructs it in the order of its fields. It seems we might get something for variants at least.
  3. Fields. A way to enumerate the fields of a tuple, record or variant case. Access their name, type and a function to project them from the product.
  4. Cases. A way to enumerate the cases of a variant in order (and in turn the fields of their product see last point)