Inductive specifications

Copy Markdown View Source

Theoria describes primitive inductive families with data structs, then admits the resulting declarations through the kernel. The DSL and spec builders are untrusted conveniences; Theoria.Kernel.add_inductive/2 is the admission boundary.

A small Bool-like family can be written with constructors only and completed with generated eliminators:

alias Theoria.{Env, Inductive, Kernel}
alias Theoria.Inductive.Spec

import Theoria.DSL

spec =
  :Switch
  |> Spec.new(term(do: sort(1)) |> elab!())
  |> Spec.constructor(:on, term(do: const(:Switch)) |> elab!())
  |> Spec.constructor(:off, term(do: const(:Switch)) |> elab!())

{:ok, spec} = Inductive.complete(spec)
{:ok, env} = Kernel.add_inductive(Env.new(), spec)

You can inspect the shape and declaration plan before admission:

Inductive.shape(spec)
#=> :bool_like

shape = Inductive.classify(spec)
shape.constructors.first.name
#=> :on

{:ok, report} = Inductive.report(spec)
report.declarations
#=> [:Switch, :on, :off, :switch_rec, :switch_ind]

For parameterized families, declare named parameters explicitly. The validator checks that constructor result arguments preserve those parameters:

u = Theoria.Level.param(:u)

type =
  term do
    forall :a, sort(^u) do
      sort(^u)
    end
  end
  |> elab!()

spec =
  :List
  |> Spec.new(type, universe_params: [:u, :v])
  |> Spec.parameter(:a, term(do: sort(^u)) |> elab!())

Indexed families can declare named indices. The current checker validates result arity, parameter preservation, and scopedness of constructor argument/index terms. Theoria.Inductive.Generate.indexed_eliminators/1 can synthesize indexed induction declarations with Theoria.Env.Recursor metadata and validated Theoria.Env.RecursorRule entries. These rules record constructor field counts, RHS templates, and index patterns; normalization uses them when an iota marker is present and refuses to reduce when explicit recursor indices do not match the selected constructor rule pattern. Environment-backed admission also requires dependencies to be present. Theoria.Library.Vec packages this fragment as the standard length-indexed vector library. Custom Vec-like specs must be checked in an environment that already contains Nat, zero, and succ.

u = Theoria.Level.param(:u)

vec_type =
  term do
    forall :a, sort(^u) do
      nat() ~> sort(^u)
    end
  end
  |> elab!()

spec =
  :Vec
  |> Spec.new(vec_type, universe_params: [:u])
  |> Spec.parameter(:a, term(do: sort(^u)) |> elab!())
  |> Spec.index(:n, term(do: nat()) |> elab!())

The built-in Bool, Nat, and List libraries use Theoria.Inductive.Generate.eliminators/1 through this same path. Their constructor targets are decomposed with Theoria.Inductive.Constructor.result/2; simple non-indexed recursor/induction types and generic reduction metadata are derived from that decomposition. Theoria.Inductive.Generate.capabilities/1 reports whether a spec is currently supported by the reducing simple-eliminator generator; indexed specs use indexed_eliminators/1 separately. Generated declarations also carry Lean-style environment metadata (Theoria.Env.Inductive, Theoria.Env.Constructor, Theoria.Env.Recursor, and Theoria.Env.RecursorRule) so tools can distinguish type formers, constructors, and recursors without relying on naming conventions. Generated eliminators use Theoria.Env.Reduction.Iota as a primitive-reduction marker; normalization selects and instantiates the matching Theoria.Env.RecursorRule RHS.