Navigation

Generic Programming

Generic programming is the payoff for description-backed data. Once a datatype exposes its constructors, fields, and description, tools can consume that structure directly instead of maintaining hand-written walkers for every domain shape.

The same generated datatype can feed a checker, a documentation surface, a dependency extractor, a schema generator, and an ornament. Those tools agree because they read the same metadata.

This is the consequence of hosting the typed description language inside Nix. There is no separate schema file to keep synchronized with the DSL, and no per-datatype validator to write. A new user datatype participates because the generic programs consume the levitated description, not a closed set of special cases known in advance.

Inspecting metadata

let
  G = fx.types.generic;
  info = G.datatype.datatypeInfo Aspect.T;
in {
  name = info.name;
  constructors = map (c: c.name) info.constructors;
}

datatypeInfo is the stable entry point for generated datatype metadata. It returns the public structure: datatype name, constructors, field names, field types, and description data needed by derivations.

Deriving descriptors

deriveDescriptor turns datatype metadata into a neutral structural description suitable for tools that do not want to depend on the HOAS representation directly:

let
  G = fx.types.generic;
in
  G.derive.deriveDescriptor Aspect.T

A descriptor is not a second source of truth. It is a view of the datatype's existing structure.

Deriving schemas

Schema derivation uses the same fields and constructor tags:

G.derive.deriveSchema Aspect.T

Use this for external validation surfaces, generated documentation, or tooling that needs to explain the shape to systems outside the kernel. The schema follows the datatype; changing the datatype changes the derived schema.

Deriving dependencies

Dependency extraction is another consumer of the same shape:

G.derive.deriveDeps Aspect.T aspect

This is useful when a domain value references other values by name or path. For an aspect declaration, the requires field can become the dependency edge list for a topological interpreter. The generic walker handles traversal; domain-specific metadata decides which fields count as references.

Diagnostics from shape

Structured diagnostics become easier when every field has a stable path. The validator can report:

[ "constructors" "aspect" "fields" "requires" 0 ]

instead of a string-only error. That path is reusable in CLI output, HTML documentation, editor integrations, and test assertions.

The path is not guessed by a custom recursive function. It is produced by the same generic descent that reads constructor and field metadata.

Avoid ad hoc walkers

The design rule is direct: if a tool needs to traverse a generated datatype, start from datatypeInfo, view, review, and the derive helpers. Do not duplicate the datatype's shape in a separate hand-written walker. The description is already the shared structure.

The ornaments chapter builds on this. An ornament refines one generated shape into another while preserving a forgetful map, and functional ornaments add a canonical way to rebuild the enriched value.