Types and indexed families¶
Indexed families make an invariant part of a value's type. A value of
Measurement(Observed) and one of Measurement(Missing) may share a family,
but a checked computation cannot confuse them. This property is the
index-carrying invariant (ICI): the constructor determines the index, and
case analysis refines it locally.
Kinds and binders¶
QVR uses square and round telescopes for different phases:
| Form | Meaning | Used by |
|---|---|---|
[A : Type] |
static type binder | families, constructors, effects, handlers, computations |
[n : Nat] |
static index binder | constructors, operations, computations |
[E : Effect] |
static effect-interface binder | declarations that abstract over an effect |
(n : Nat) |
refinable family index | family declarations and case motives |
(x : T) |
runtime value parameter | computations and handler clauses |
Static arguments are supplied in brackets, as in length[Real, S(Z)](xs).
Runtime values are supplied in parentheses. An integer literal may fill a
Nat static binder, so window[32](xs) is valid when window binds
[n : Nat].
The built-in value types are Unit, Bool, Int, Real, String,
LogWeight, Tensor[A]([d1, ...]), Sampleable[A], and Site[A]. A product
type is written A * B; a tuple value is written (a, b) and projected as
pair[0] or pair[1].
Closed index sorts¶
An index declaration introduces a closed sort and all of its constructors:
Index constructors may be nullary or recursive. Because the sort is closed,
the checker knows every constructor that a case must cover. Index values are
static terms rather than runtime integers; use a Nat literal only when a
declaration explicitly binds a Nat.
Family declarations¶
A family may have uniform static parameters in brackets and refinable indices in parentheses:
index Nat = Z | S(Nat)
family Vec[A : Type](n : Nat) : Type
constructor Nil : Vec[A](Z)
constructor Cons[m : Nat] : A * Vec[A](m) -> Vec[A](S(m))
A is uniform: a case branch cannot change which element type the vector
uses. n is refinable: matching Nil proves that the branch index is Z,
while matching Cons[m] proves that it is S(m). Constructor fields precede
->; a nullary constructor omits them.
Family and constructor names occupy distinct namespaces. A constructor's static arguments include the family's uniform parameters followed by its own telescope. Thus a three-element real vector can be built as follows:
index Nat = Z | S(Nat)
family Vec[A : Type](n : Nat) : Type
constructor Nil : Vec[A](Z)
constructor Cons[m : Nat] : A * Vec[A](m) -> Vec[A](S(m))
define triple(a : Real, b : Real, c : Real) : Vec[Real](S(S(S(Z)))) !{} =
let last = construct Cons[Real, Z](c, construct Nil[Real]() as Vec[Real](Z)) as Vec[Real](S(Z))
let pair = construct Cons[Real, S(Z)](b, last) as Vec[Real](S(S(Z)))
return construct Cons[Real, S(S(Z))](a, pair) as Vec[Real](S(S(S(Z))))
construct and as are both required. The annotation closes the expected
family application and prevents an ambiguous constructor from acquiring a
type from unrelated context.
Indexed case analysis¶
A case expression states a motive, that is, the result type as a function of the scrutinee's indices:
index Nat = Z | S(Nat)
family Vec[A : Type](n : Nat) : Type
constructor Nil : Vec[A](Z)
constructor Cons[m : Nat] : A * Vec[A](m) -> Vec[A](S(m))
define length[A : Type, n : Nat](xs : Vec[A](n)) : Int !{} =
case xs motive (m : Nat) => Int
Nil =>
return 0
Cons[k](head, tail) =>
let rest <- length[A, k](tail)
return 1 + rest
The branch binder k is a rigid local index. It may determine the type of
tail and the static argument of the recursive call, but it cannot escape the
branch in the result type. Coverage is conservative: a constructor may be
omitted only when closed-constructor equality proves that branch impossible.
This rule permits a total head without a Nil branch:
index Nat = Z | S(Nat)
family Vec[A : Type](n : Nat) : Type
constructor Nil : Vec[A](Z)
constructor Cons[m : Nat] : A * Vec[A](m) -> Vec[A](S(m))
define head[A : Type, n : Nat](xs : Vec[A](S(n))) : A !{} =
case xs motive (m : Nat) => A
Cons[k](first, rest) =>
return first
Vec[A](S(n)) cannot have been built by Nil, whose result index is Z.
The omitted branch is thus impossible, rather than a partial match.
Pure expressions¶
Computation values and program expressions use the same checked primitive
registry. The eager PyTorch compiler has an explicit native-only extension;
those names do not silently enter QIEC.
Operators associate left within each precedence level:
| Precedence, low to high | Forms |
|---|---|
| Boolean disjunction | || |
| Boolean conjunction | && |
| comparison | ==, !=, <, <=, >, >= |
| additive | +, - |
| multiplicative | *, /, % |
| unary | -, not |
The registry includes typed scalar primitives, tensor reductions, rowwise
operations, and the fixed-shape collection forms map, fold, length, and
logsumexp_over. traverse is effectful computation syntax rather than a
pure value. The complete inventory and type restrictions are in
Finite collection expressions and the
let-expression guide.
Integer division and remainder truncate toward zero on every target. Mixed
integer-real arithmetic inserts an explicit conversion when the surrounding
form permits it. Write a comparison with a negative literal as x < (-1)
because <- is the effectful binding token.
List literals build tensors. [1, 2, 3] has integer elements;
[1.0, 2.0, 3.0] has real elements; nested lists add dimensions. Distribution
family applications build Sampleable values, site("x") builds a typed site,
and log_prob(distribution, value) returns a LogWeight.
Common failures¶
- Missing branch. Add the constructor unless its result indices are provably incompatible with the scrutinee.
- Escaped index. Return a type independent of a branch-local static binder, or package the witness inside a family whose outer type is fixed.
- Wrong constructor telescope. Supply uniform family parameters first, then constructor-local parameters.
- Value/static confusion. Put types and indices in
[...]; put runtime values in(...). - Shadowed primitive type. A telescope binder named
Realis a local type variable, not the primitive. Rename it unless that abstraction is intended.
Continue with Computations for recursion and binding, or with Effects and handlers for indexed computations that perform requests.