Outputs and Requirements
Grammar
out_decl = "out" identifier [ ":" type_expr ] ":=" expression
[ cell_filter ] [ "require" "{" { requirement } "}" ] ";".
requirement = [ "@" identifier ] expression ";".
An out declares a new cell computed by exactly one expression: there’s no alternative
binding to choose between, unlike a relationship. Its dependencies are
deduced the same way. The : type_expr annotation is
optional; when absent, the output’s type is inferred from the initializer, the same rule
built-in types and inference gives for a plain cell. An out may
also carry a filter clause, exactly like a plain cell; see the filters chapter.
sheet area_demo {
source width = 10 filter 0..=100;
source height = 20 filter 0..=100;
out area := width * height;
}
An output cell can be read anywhere, written nowhere
out shares one namespace with cell: declaring out result := ...; after (or before) a
cell result in the same sheet is a duplicate-name error, exactly like two cell declarations
would be. An output cell is an ordinary cell for reading: a later declaration in the same
sheet can reference an earlier out by name in its own expression — as a relationship input, a
conditional’s match subject, a filter argument, or another out’s own initializer — exactly
like referencing any other already-declared cell. What stays restricted is writing: an
output’s cell can never be produced by more than one method, and can never be written directly,
not by a host write, not by a relationship binding, not by a second out. Its value
comes from its own initializer and nothing else. The initializer is purely functional;
resolving the sheet may reuse its result rather than evaluate it again:
sheet s {
source width: i32 = 10;
out area := width * 2;
out doubled_area := area * 2;
}
Requirements: diagnostics, not gates
require is not an outputs-only mechanism: a require { ... } block can trail a plain cell
or a source declaration’s initializer too, with the same meaning described here
— see cell declarations and source cells for those forms. A
filter clause is just as unrestricted — see
a filter on an output cell for a filter attached to an out
declaration. Trailing an out’s initializer, require { ... } names zero or more boolean
checks. Each requirement’s own dependencies are deduced separately from the output’s
initializer; a requirement commonly reads the output’s own value by name, alongside whatever
other cells it needs:
sheet area_demo {
source width: i32 = 10;
source height: i32 = 20;
out area: i32 := width * height require {
@not_too_big area <= 300;
};
}
Requirements are purely functional predicates of their input values and immutable constants, with no externally observable side effects or mutable external dependencies. Equal inputs produce equivalent results or errors. There is no guarantee that a requirement executes, or when or how often it executes; current diagnostics may use a previously computed result.
A failed requirement never stops the sheet from resolving, and never stops area from being
computed and readable; a host can query which requirements are currently failing precisely
because nothing else in the sheet notices a requirement failing on its own (see the
host embedding API). A requirement’s @name is
optional — present only when an application wants to report which requirement failed; it
happens to read naturally when it echoes a cell name (@not_too_big, @width_max), but it
isn’t a cell reference and doesn’t have to match one.
Multiple requirements
An output can list any number of requirements; a host can query exactly the ones currently failing, identified individually (see the host embedding API):
sheet bounds_demo {
source x: i32 = 50;
out clamped: i32 := x require {
@not_negative clamped >= 0;
@not_too_big clamped <= 100;
};
}