Dependent
Dependent contracts: Pi (Π), Sigma (Σ), Certified, Vector, DepRecord. Grounded in Martin-Löf (1984) "Intuitionistic Type Theory".
Certified
Certified value: Σ(v:A).Proof(P(v)).
A dependent pair where:
fst : A — the value
snd : true — proof witness (must be true AND predicate must hold)
The second component's type depends on the first: it checks both
that the proof is true and that predicate(fst) holds.
Certified is a first-order contract — both components are concrete data, so the contract is checked immediately and completely (like Sigma). The guard IS full membership.
Construction:
.certify v— pure smart constructor (throws on invalid).certifyE v— effectful smart constructor (sendstypeCheckeffects).check— inherited from Sigma (full dependent pair check).validate— inherited from Sigma (effectful introduction check)
The .certifyE constructor is NOT an introduction check — it's a
convenience that takes a raw value, evaluates the predicate, and
produces the Sigma pair { fst = v; snd = true; }. The actual
introduction check (.validate) is inherited from Sigma and verifies
an already-formed pair.
DepRecord
Dependent record type built on nested Sigma.
Schema is an ordered list of { name; type; } where type can be:
- A Type (static field)
- A function (
partial-record → Type) for dependent fields
Isomorphic to nested Sigma types:
{ a : A, b : B(a) } ≅ Σ(a:A).B(a)
{ a : A, b : B(a), c : C(a,b) } ≅ Σ(a:A).Σ(b:B(a)).C(a,b)
Values are nested Sigma pairs:
{ fst = a; snd = { fst = b; snd = c; }; }
Inherits from Sigma: .validate (effectful), .proj1, .proj2,
.pair, .pairE, .curry, .uncurry.
Use .pack to convert flat attrset → nested Sigma value.
Use .unpack to convert nested Sigma value → flat attrset.
Pi
Dependent function type Π(x:A).B(x).
Arguments:
domain— Type Acodomain— A-value → Type (type family B indexed by domain values)universe— Universe level (explicit parameter — see below)name— optional display name
== Higher-order contract with algebraic effects ==
Pi is a HIGHER-ORDER CONTRACT (Findler & Felleisen 2002). Higher-order contracts check function values differently from data values: a data contract is verified immediately and completely, but a function contract is verified incrementally at each application site. This is the standard, correct strategy for function contracts — not a deficit.
The (Specification, Guard, Verifier) triple for Pi:
Guard (check): builtins.isFunction — the immediate first-order
part of the contract. Soundly rejects non-functions.
Verifier (validate): effectful guard (auto-derived, 1 arg) — wraps
the guard in a typeCheck effect for blame tracking.
Elimination (checkAt): deferred contract check (2 args) — verifies a
specific application f(arg) by sending typeCheck
effects for both domain (arg : A) and codomain
(f(arg) : B(arg)).
This is precisely the Findler-Felleisen decomposition: the immediate
part (isFunction) is checked at introduction; the deferred part
(domain + codomain) is checked at each elimination site via checkAt.
== Adequacy ==
check f ⟺ all typeCheck effects in (validate f) pass
Both check and validate verify the introduction form (is it a function?).
checkAt verifies individual applications — the deferred contract.
== Universe level ==
Universe level is an explicit parameter. In MLTT, the level is computed
as max(i, sup_{a:A} level(B(a))) by inspecting the syntax of B.
For types with explicit kernelType, the kernel computes and verifies
levels via checkTypeLevel. The explicit universe parameter provides
the level for the surface API's .universe field.
== MLTT rule mapping ==
Formation: Pi.value { domain, codomain, universe }
Introduction check: .check (guard: isFunction)
Introduction verify: .validate (effectful guard, auto-derived)
Elimination: .apply (pure), .checkAt (effectful, deferred contract)
Computation: β-reduction (Nix evaluation)
Operations:
.checkAt f arg— deferred contract check at elimination site.apply arg— pure elimination: compute codomain type B(arg).compose f other— compose Pi types (requires witness function).domain— the domain type A.codomain— the type family B
Sigma
Dependent pair type Σ(x:A).B(x).
Arguments:
fst— Type A (type of the first component)snd— A-value → Type (type family for the second component)universe— Universe level (explicit parameter)name— optional display name
Values are { fst; snd; } where fst : A and snd : B(fst).
== First-order contract — guard is exact ==
Sigma is a FIRST-ORDER CONTRACT: both components are concrete data,
so the contract is checked immediately and completely. The guard
(check) IS full membership — there is no over-approximation.
Guard (check): fst:A ∧ snd:B(fst) — exact. G = ⟦Σ(x:A).B(x)⟧.
Verifier (verify): decomposed effectful check — sends separate
typeCheck effects for fst and snd for blame tracking.
This contrasts with Pi where the guard over-approximates (isFunction)
because functions are higher-order. Sigma pairs are data — the
dependent relationship (snd's type depends on fst's value) can be
fully verified because both values are available.
Adequacy:
T.check v ⟺ all typeCheck effects in T.validate v pass
Under the all-pass handler. The guard is exact and the decomposed
verifier sends individual typeCheck effects per component — the all-pass
handler's boolean state tracks whether all passed. Totality: if the input
is structurally malformed (not an attrset, missing fst/snd), verify falls
back to a single typeCheck for the whole type — failure goes through the
effect system, never crashes Nix.
Universe level is an explicit parameter (computing
sup_{a:A} snd(a).universe requires evaluating the type family on
all domain values, same as Pi).
== MLTT rule mapping ==
Formation: Sigma.value { fst, snd, universe }
Introduction: .check (exact guard), .validate (effectful, decomposed)
Elimination: .proj1 (π₁), .proj2 (π₂)
Computation: π₁(a,b) ≡ a, π₂(a,b) ≡ b
Operations:
.proj1 pair— first projection π₁.proj2 pair— second projection π₂.pair a b— smart constructor (throws on invalid).validate v— effectful: decomposed typeCheck effects for blame.pairE a b— effectful smart constructor.pullback f g— contravariant predicate pullback (see below).curry/.uncurry— standard Sigma adjunction.fstType— the type A.sndFamily— the type family B
Vector
Length-indexed list type family, built on Pi.
Vector(A) = Π(n:Nat).{xs : List(A) | |xs| = n}
This is the correct Martin-Löf encoding: Vector IS a Pi type.
It inherits .validate (effectful), .compose, .apply, .domain, .codomain
from Pi.
Usage:
Vector elemType # the Pi type family (Nat → SizedList)
(Vector elemType).apply 3 # specific type for length 3