Finite collection expressions¶
QVR has two operations for applying a function across a tensor. A pure
map builds a value; an effect-aware traverse sequences computations. We
refer to this distinction as the finite collection contract (FCC). The
contract keeps tensor shape in the type while making the order of effects
explicit.
Operation reference¶
| Form | Position | Result | Main restriction |
|---|---|---|---|
map(xs, x -> body) |
pure expression | tensor with the leading extent of xs |
body is pure |
traverse(xs, x -> computation(...)) |
right side of <- |
tensor of computation results | leading extent is statically known |
fold(xs, init, acc -> x -> body) |
pure expression | type of init |
leading extent is statically known |
length(xs) |
pure expression | Int |
leading extent is a literal |
logsumexp_over(xs, x -> score) |
pure expression | Real |
score returns one Real per item |
All five operations use the leading axis. If xs has type
Tensor[A]([n, d]) and the lambda returns Tensor[B]([k]), map and
traverse return Tensor[B]([n, k]).
Pure map¶
map accepts an inline lambda or a lambda named by a local pure binding:
define standardized(xs : Tensor[Real]([3]), center : Real, scale : Real) : Tensor[Real]([3]) !{} =
let z_score = x -> (x - center) / scale
return map(xs, z_score)
The lambda may capture surrounding pure values such as center and scale.
Its parameter shadows an outer name with the same spelling. QIEC lowers the
form to a typed comprehension, so the result remains available to the
reference machine and to targets with comprehension support.
Effect-aware traverse¶
Use traverse when each item must be passed to a named computation. Its
surface is a computation call and thus uses <-:
define center(x : Real, mean : Real) : Real !{} =
return x - mean
define center_all(xs : Tensor[Real]([3]), mean : Real) : Tensor[Real]([3]) !{} =
let centered <- traverse(xs, x -> center(x, mean))
return centered
The lambda body must be one named computation call. The callee's effect row
joins the row of the enclosing computation. QIEC expands a fixed collection
into calls and binds in source order, which gives each call its own stable
structural path. This expansion also means that existing interpreters and
transpilers need no separate traverse runtime primitive.
The current form does not pass static arguments to the lambda's callee. Wrap a specialized call in a monomorphic helper when a computation has a static telescope.
Fold and derived reductions¶
fold is a left fold. The lambda is curried with the accumulator first and
the current item second:
define weighted_total(values : Tensor[Real]([3]), weights : Tensor[Real]([3])) : Real !{} =
let products = map([0, 1, 2], i -> values[i] * weights[i])
return fold(products, 0.0, total -> value -> total + value)
logsumexp_over(xs, f) is the stable log-space reduction of map(xs, f).
It is useful for finite Bayesian normalization:
define normalize(log_joint : Tensor[Real]([3])) : Tensor[Real]([3]) !{} =
let log_evidence = logsumexp_over(log_joint, score -> score)
return map(log_joint, score -> exp(score - log_evidence))
length(xs) returns the leading extent as an Int. A statically polymorphic
extent is an index term rather than a runtime integer, so it must be
specialized before length or fold can use it.
Dynamic filtering¶
The eager PyTorch expression evaluator retains filter(xs, predicate) for
host-only workflows. Checked QIEC tensors have fixed shape, so an unmasked
filter would need an existential length or a ragged-tensor type. The checked
surface thus rejects filter with the stable
qiec-collection:filter:dynamic-shape diagnostic. Use a Boolean mask and a
reduction when the output can stay at its original shape.
Diagnostics and target support¶
Collection errors are ordinary checked diagnostics: wrong arity, a non-tensor
source, a non-lambda function argument, a dynamic leading extent, or a result
of the wrong type fails during qvr check. A program that cannot elaborate is
reported as qiec-program-gap; it is not silently omitted from the checked
module.
The reference machine executes every FCC form. Dynamic targets lower the
resulting comprehensions, gathers, calls, and binds through the common QIEC
runtime. Stan, BUGS, and JAGS may reject those core capabilities. Run
qvr check --target TARGET to test the selected backend before transpiling.
See the finite-grid Bayes tutorial
for a complete posterior calculation, and Computations for
the distinction between = and <-.