[ANN] Typegist 0.0.0

Hello,

It’s my pleasure to announce the first release of typegist:

Typegist represents the essence of OCaml types as values.

This dynamic type representation can be used to devise generic type-indexed functions – value serializers, printers, parsers, differs, random generators, editors, ffi glue, etc. Any accessible type can be described up to the limits defined by its public interface.

Typegist does not model OCaml’s type language in full detail, but focuses on a core structural subset decorated with typed-indexed metadata to provide an ergonomic interface for both producers and processors of the representation.

Typegist is distributed under the ISC license. It has no dependencies.

As mentioned above these values only partially model OCaml’s type definition language, that’s the reason why they are Type.Gist.t and not Type.Repr.t values. You should see typegist as a data interfacing language for your types rather than a faithful or canonical representation of your types (which I find less useful in practice).

The representation special cases and annotates some of the Stdlib types: being too generic and losing all semantics in favour of generalized abstract non-sense is undesirable when you interface with other systems. For example. You want list values to show up as arrays in JSON, not as nested cons case objects. You want None to map to null not to a constant case object. You want string values that hold textual data to show up as plain JSON strings rather than hex digits or base64. Etc.

This means that part of the representation is decidedly ad-hoc. It balances precision and genericity while making it reasonably easy to devise your own gist processors without getting bogged into pointless details of OCaml’s type expression language.

So next time it’s time for you to write an M.pp : t Fmt.t function, write an M.gist : t Type.Gist.t instead. You’ll get your printer and more. A companion release of jsont was made with the new optional jsont.typegist library that translates type gists into jsont JSON types for your JSON serialization pleasure (if that exists).

While I don’t expect typegist to change much, it hasn’t been used in anger yet – but I’ll waste no time. It’s again a design that has been rotting for too long in a repo. This means that changes in the representation could still occur based on feedback if more precision is needed or better representation are found. However I’d expect such changes to mostly affect gist processors. Get in touch on the issue tracker if you run into difficulties or improvements.

I have no plan to propose any mean to automate gist derivations from type definitions, but some people have expressed interest in doing that in the past.

Happy typed-indexed programming!

This first release was made possible thanks to a grant from the OCaml Software Foundation. I also thank my donors for their support.

Homepage: https://erratique.ch/software/typegist
Docs: https://erratique.ch/software/typegist/doc or odig doc typegist
Install: opam install typegist (opam PR)

Best,

Daniel


P.S. The API makes use – for good – of every new type gimick that was introduced in OCaml 5.5 :–)

This is very cool :slight_smile:


For people who are interested in this kind of project in other languages, the example

      Type.Gist.record "Item.t" make
      |> Type.Gist.field "task" Type.Gist.utf_8_string task
      |> Type.Gist.field "status" Status.gist status
      |> Type.Gist.field "tags" Type.Gist.(list utf_8_string) tags
      |> Type.Gist.finish

instantly gave me a sense of deja-vu from a past life making Minecraft mods[1]. That game’s Java edition heavily utilizes Mojang/DataFixerUpper, with code that ends up looking like

   public static final MapCodec<JigsawStructure> CODEC = RecordCodecBuilder.mapCodec(
         i -> i.group(
               settingsCodec(i),
               StructureTemplatePool.CODEC.fieldOf("start_pool").forGetter(j -> j.startPool),
               Identifier.CODEC.optionalFieldOf("start_jigsaw_name").forGetter(j -> j.startJigsawName),
               Codec.intRange(0, 20).fieldOf("size").forGetter(j -> j.maxDepth),
               HeightProvider.CODEC.fieldOf("start_height").forGetter(j -> j.startHeight),
               Codec.BOOL.fieldOf("use_expansion_hack").forGetter(j -> j.useExpansionHack),
               Types.CODEC.optionalFieldOf("project_start_to_heightmap").forGetter(j -> j.projectStartToHeightmap),
               JigsawStructure.MaxDistance.CODEC.fieldOf("max_distance_from_center").forGetter(j -> j.maxDistanceFromCenter),
               Codec.list(PoolAliasBinding.CODEC).optionalFieldOf("pool_aliases", List.of()).forGetter(j -> j.poolAliases),
               DimensionPadding.CODEC.optionalFieldOf("dimension_padding", DEFAULT_DIMENSION_PADDING).forGetter(j -> j.dimensionPadding),
               LiquidSettings.CODEC.optionalFieldOf("liquid_settings", DEFAULT_LIQUID_SETTINGS).forGetter(j -> j.liquidSettings)
            )
            .apply(i, JigsawStructure::new)
      )
      .validate(JigsawStructure::verifyRange);

to provide exactly the same kinds of generic operations like json de/serialization etc.

Perhaps unsurprisingly, they do seem to share some common ancestors if you walk through the citations, though they quickly get a bit too Haskell-y for my own deep understanding


  1. As an aside to my aside, this is not the first time I’ve run into an unexpected Minecraft modding/OCaml overlap ↩︎

The example provided in the Typegist documentation is of a pretty printer ( See index (typegist.index) ) .

No doubt you can do other things with Typegist but if you want an out-of-the-box pretty printer ability see [ANN] Introcaml (alpha): Polymorphic Printing and Introspection for OCaml

cc: @let-def