Computations¶
A define declaration introduces a named checked computation. Its signature
separates static binders, runtime parameters, result type, and effect row:
define clamp(x : Real, low : Real, high : Real) : Real !{} =
if x < low then
return low
else
if x > high then
return high
else
return x
This four-part signature is the computation contract (CC). Calls are checked only against the contract; the body is then checked to ensure that it returns the declared type and performs no effects outside the row.
Declaration form¶
Both telescopes are optional. A computation with no value parameters still
writes (). Static binders may range over Type, an index sort, or Effect.
A Nat binder may receive an integer literal at a call site.
define identity[A : Type](value : A) : A !{} =
return value
define four() : Int !{} =
let value <- identity[Int](4)
return value
Pure and effectful binding¶
QVR deliberately distinguishes two forms:
| Form | Right-hand side | Meaning |
|---|---|---|
let x = value |
pure expression | bind a value without sequencing an effect |
let x <- computation |
call, request, or resumption | run a computation, then bind its result |
An inline computation may appear without a binding when its result is Unit:
instance score : Score
define penalize(x : Real) : Real !{score} =
let squared = x * x
perform score.add(log_prob(Normal(0.0, 1.0), x))
return squared
There is no implicit coercion from a call to a value expression. If helper
is declared with define, write let result <- helper(args), even when its
row is empty.
The same distinction governs collections. map(xs, x -> value) is pure and
uses =, while traverse(xs, x -> helper(x)) sequences computation calls and
uses <-. The latter joins helper's effects into the caller's row. See
Finite collection expressions for folds,
normalization, and shape restrictions.
Calls and row propagation¶
One call syntax covers ordinary application and recursion:
define triangle(n : Int) : Int !{} =
if n <= 0 then
return 0
else
let rest <- triangle(n - 1)
return n + rest
A caller's row must include the callee's residual row after static arguments are substituted. The checker follows the reachable call graph, so a pure wrapper around an effectful callee remains effectful unless it handles the effect.
Recursion uses the reference machine's explicit stack. Set an execution fuel
limit while developing a recursive computation; exhaustion reports
qiec-run-fuel rather than overflowing the host stack.
Requests¶
A request names an instance and an operation and may supply operation-level static arguments:
instance random : Random
define draw_normal(location : Real, scale : Real) : Real !{random} =
let value <- perform random.sample[Real](site("x"), Normal(location, scale))
return value
The [Real] argument specializes the operation's result type A. The
instance's interface parameters were already fixed by its instance
declaration. The checker substitutes both telescopes before it verifies the
request arguments and result.
Handling and scoped instances¶
handle INSTANCE with HANDLER[STATICS] in installs a lexical handler around
its indented body. with instance NAME : EFFECT in creates an instance for
its body. Both forms contain computations and thus participate in row
checking:
effect Echo
echo : String -> String
handler identity_echo for Echo : String -> String [coverage=total, forwards=none, implementation=authored]
return value =>
return value
echo(value : String) resumes 1 =>
resume(value)
define echoed(value : String) : String !{} =
with instance local : Echo in
handle local with identity_echo in
let result <- perform local.echo(value)
return result
See Effects and handlers for coverage, forwarding, and resumption grades.
Conditionals and indexed cases¶
if branches at computation level and evaluates only the selected branch.
Both branches must return the same type; their effect rows are joined.
case eliminates a family value and carries an explicit motive. Constructor
patterns may bind static indices and runtime fields. The result of every
reachable branch must inhabit the motive under that branch's refinement. See
Types and indexed families
for the full form.
Constructor values¶
Three application-shaped forms remain syntactically distinct:
| Form | Position | Example |
|---|---|---|
| computation call | computation | helper[Real](x) |
| effect request | computation | perform random.sample[Real](...) |
| constructor | value | construct Present(x) as Measurement(Observed) |
This separation prevents an unqualified operation or a constructor from being resolved as an ordinary call.
Built-in values and distributions¶
The same pure-expression registry is available here and in a program.
Distribution applications such as Normal(mu, sigma),
Dirichlet([1.0, 2.0]), PointMass(0), or
Mixture([0.3, 0.7], [PointMass(0), Poisson(rate)]) produce typed
Sampleable values. Restrict/Truncate, Normalize,
Transformed/Pushforward, and Independent are distribution families too,
so the measure algebra remains explicit in the checked term.
The reference backend implements every family in the semantic registry. Parameter order and support follow the continuous-family reference; target renderers apply any host-specific parameter conversion at emission.
Entry-point status¶
Every user-authored define is executable unless it is an internal helper
generated by elaboration. List entries with:
An entry with a nonempty residual row also needs handlers supplied by the
selected runtime provider. Authored handlers inside the body need no external
attachment. Static binders must be closed at invocation with repeated
--static NAME=TERM options.
For the invocation forms and stable diagnostics, continue to Execution and tooling.