Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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;
    };
}