GADTs and indexed families¶
Declarations¶
didactic.gadt.GADT ¶
A complete generalized algebraic theory declaration.
GADT is a builder with a deliberate freeze point. Sorts, constructors,
operations, and equations may be added until :meth:compile is called.
Compilation seals the declaration, builds a panproto.Theory value, and
runs Panproto's full theory typechecker before returning it.
equations
property
¶
equations: tuple[Equation, ...]
Return ordinary equations in declaration order.
definition_count
property
¶
Return the number used to derive the next definition name.
family_named ¶
family_named(name: str) -> Family | None
Return the family declared under name, if any.
operation_named ¶
operation_named(name: str) -> Operation | None
Return the operation declared under name, if any.
sort ¶
sort(
name: str,
*,
closed: bool = False,
kind: JsonValue = "Structural",
) -> Family
Declare a sort with no indices: Ty = language.sort("Ty", closed=True).
An indexed sort is declared with :meth:family, whose signature
carries the telescope.
family ¶
family(
name: str,
*,
closed: bool = False,
kind: JsonValue = "Structural",
**parameters: InputSpec,
) -> Family
Declare an indexed sort from keyword indices.
language.family("Expr", t=Ty, closed=True) declares Expr(t : Ty).
Keyword order is the telescope. A later index's sort may depend on an
earlier one, as a lambda over its name::
language.family("Term", context=Context, type=lambda context: Type[context])
constructor ¶
Declare an introduction form.
returns names the family the constructor introduces, and that
family's constructor list gains the name::
language.constructor(
"IntLit", value=El[int_code()], returns=Expr[int_code()]
)
operation ¶
Declare an ordinary operation.
language.operation("int_value", returns=El[int_code()]) declares a
nullary operation producing a carrier value.
eliminator ¶
eliminator(
name: str,
*,
returns: SortSpec,
body: Body | None = None,
**inputs: InputSpec,
) -> Operation
Declare an eliminator, and define it when given a body.
returns is the motive and may depend on earlier inputs. The body
is a lambda over every input, in order, returning the term that
defines the eliminator, usually a :func:match over one input::
language.eliminator(
"evaluate",
t=Ty,
expression=lambda t: Expr[t],
returns=lambda t: El[t],
body=lambda t, expression: match(expression, IntLit=lambda v: v, ...),
)
Without a body the eliminator is declared only, and
:meth:Operation.define supplies one later.
equation ¶
equation(
name: str,
lhs: Term,
rhs: Term,
*,
directed: bool = False,
executable: bool = False,
) -> Equation
Declare an equality or directed rewrite.
rewrite ¶
Declare a directed equation used by normalization.
infer_sort ¶
Infer the sort of an application, variable, let, or case term.
didactic.gadt.Family
dataclass
¶
Family(
owner: GADT,
name: str,
parameters: tuple[Parameter, ...],
closed: bool,
kind: JsonValue = "Structural",
_constructors: list[str] = _empty_constructor_names(),
)
A possibly indexed sort declaration owned by one :class:GADT.
didactic.gadt.Operation
dataclass
¶
Operation(
owner: GADT,
name: str,
inputs: tuple[Parameter, ...],
output: SortExpr,
role: Literal[
"constructor", "operation", "eliminator"
] = "operation",
motive: Motive | None = None,
)
A constructor, ordinary operation, or eliminator declaration.
explicit_inputs
property
¶
explicit_inputs: tuple[Parameter, ...]
Return the arguments callers must supply in term applications.
define ¶
Define an eliminator by an oriented, typechecked equation.
body is a lambda over every input, in order, returning the
defining term. It runs once with a symbolic variable per input. The
equation applies the eliminator to those variables on the left and
is included among the equalities sent to Panproto and among the
executable reduction rules.
didactic.gadt.Implicit
dataclass
¶
Implicit(sort: SortSpec)
An input Panproto infers from the explicit ones: n=Implicit(Nat).
The wrapped sort may itself depend on earlier inputs.
didactic.gadt.Motive
dataclass
¶
Motive(result: SortExpr)
The result-sort expression of an eliminator.
A motive may mention any earlier eliminator input. Panproto refines those variables separately in each case branch, which is what permits branch bodies at different indices to inhabit one dependent result.
didactic.gadt.Equation
dataclass
¶
A named equality between terms.
Terms¶
didactic.gadt.Term ¶
didactic.gadt.App
dataclass
¶
App(op: str, args: tuple[Term, ...] = ())
Bases: Term
An operation applied to zero or more argument terms.
didactic.gadt.Hole
dataclass
¶
Bases: Term
A typed hole reported by Panproto during theory checking.
didactic.gadt.Case
dataclass
¶
didactic.gadt.Branch
dataclass
¶
Branch(
constructor: str, binders: tuple[str, ...], body: Term
)
One constructor branch of a case term.
didactic.gadt.SortExpr
dataclass
¶
SortExpr(name: str, args: tuple[Term, ...] = ())
A sort head applied to zero or more term indices.
didactic.gadt.term_from_spec ¶
term_from_spec(spec: JsonValue) -> Term
Decode a term previously produced by :meth:Term.to_spec.
| PARAMETER | DESCRIPTION |
|---|---|
spec
|
A tagged Panproto term record.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Term
|
The corresponding immutable term. |
didactic.gadt.match ¶
Case analysis, one keyword per constructor: IntLit=lambda v: v.
Each branch lambda runs once with a fresh variable per parameter, and its parameters are the branch's binders. Inside an eliminator body the constructor names are checked against the scrutinee's family at once, naming the valid set on a miss, and each branch's binder count against its constructor. Outside one, those are left to Panproto's checker.
didactic.gadt.let ¶
Bind a term locally: let(int_value(), lambda x: IntLit(x)).
The lambda's single parameter is the binder. A unary operation may
stand in for the lambda: let(int_value(), IntLit).
Declaration types¶
didactic.gadt.Sort ¶
A sort given directly: an applied family such as Expr[t] or a bare
family with no indices such as Ty.
didactic.gadt.SortSpec ¶
A sort, or a lambda over earlier inputs producing one.
didactic.gadt.InputSpec ¶
What one keyword argument of a declaration may be.
didactic.gadt.Body ¶
Body = (
Operation
| Callable[[], Term]
| Callable[[Var], Term]
| Callable[[Var, Var], Term]
| Callable[[Var, Var, Var], Term]
| Callable[[Var, Var, Var, Var], Term]
| Callable[[Var, Var, Var, Var, Var], Term]
| Callable[[Var, Var, Var, Var, Var, Var], Term]
| Callable[[Var, Var, Var, Var, Var, Var, Var], Term]
| Callable[
[Var, Var, Var, Var, Var, Var, Var, Var], Term
]
)
A term over some binders: an eliminator body, a case branch, a let body.
Model integration¶
didactic.gadt.Universe ¶
A finite Tarski universe for value-indexed schema fields.
The generated GADT contains a closed code sort and an open El(code)
carrier. Each supplied case receives a nullary code constructor, a value
sort, and an injection from that value sort into the corresponding
carrier. The open carrier permits Model field projections while the closed
code sort retains exhaustive case analysis.
didactic.gadt.IndexedBy
dataclass
¶
IndexedBy(
family: Family,
index_fields: tuple[str, ...],
cases: tuple[
tuple[tuple[Term, ...], TypeForm], ...
] = (),
case_labels: tuple[tuple[str, ...], ...] = (),
)
Metadata linking a field's sort to one or more sibling fields.
Put an IndexedBy value inside typing.Annotated. The base Python
annotation remains visible to static type checkers, while this marker
supplies the dependent family and the model fields used as its indices.
Optional cases associate concrete index terms with Python annotations,
allowing runtime values to be validated at the refined type.
didactic.gadt.indexed_by ¶
indexed_by(
family: Family,
*index_fields: str,
cases: Mapping[Term | tuple[Term, ...], TypeForm]
| None = None,
) -> IndexedBy
Construct an :class:IndexedBy marker.
A one-index family accepts term keys directly. Multi-index families use a tuple of terms per case.
Errors¶
didactic.gadt.GADTDeclarationError ¶
Bases: ValueError
A locally detectable error in a GADT declaration.
didactic.gadt.GADTReductionError ¶
Bases: RuntimeError
A symbolic reduction could not make safe progress.