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

Relationships Continued: Destructuring and Self-Referencing Methods

Destructuring bindings

A binding’s left-hand side can name more than one output cell by parenthesizing it, in which case the right-hand side must be a tuple expression of matching arity, split element-wise:

sheet rect_demo {
    source width: f64 = 10.0;
    source height: f64 = 4.0;
    cell area: f64;
    cell perimeter: f64;

    relationship {
        (area, perimeter) := (width * height, 2.0 * (width + height));
    }
}

(a, b) := ... and the one-element (a,) := ... (trailing comma mandatory, matching Rust’s own 1-tuple pattern) both destructure; a bare a := ... or the equivalent single parenthesized (a) := ... (mere grouping, no comma) instead binds the right-hand side’s whole result (including a tuple-typed one) directly to the one named cell. Destructuring and direct-bind are otherwise governed by the same type-matching rules as any other binding: each output’s declared type must structurally match what the expression actually produces.

Self-referencing methods

A method’s expression may reference the very cell it writes — a self-referencing method — which the rules a relationship’s methods must satisfy section already noted is explicitly allowed: a cell may appear in both a method’s inputs and its own outputs. Each time the sheet resolves, a self-referencing method reads its own cell’s source value — never a previous round’s derived value — the same source/derived split the raw value is never lost section already introduced for filters:

sheet clamp_via_relationship {
    cell level: i32 = 0;

    relationship {
        level := min(level, 0);
    }
}

Writing level above never applies the clamp itself — exactly like a filter, the correction happens live, the next time the sheet resolves, against level’s own raw value, and that raw value survives underneath the clamp forever.

Self-referencing methods must be idempotent

A self-referencing method exists to correct its own cell into whatever set of values the relationship enforces. That only makes sense if reapplying the method to its own already-corrected output leaves it unchanged — f(f(x)) == f(x). min(level, 0) above satisfies this: once level is at most 0, computing min of that value and 0 again produces the same value.

The solver never checks this — nothing about add_relationship inspects a method’s function for idempotence, and nothing about propagate would refuse to resolve a sheet whose self-referencing method isn’t idempotent. It is purely a correctness obligation on the sheet author. A self-referencing binding built from a genuinely non-idempotent operation — a literal swap between two cells’ current values, for instance, rather than a one-sided correction like min/max/ clamp — has no single well-defined corrected value for the solver to settle on.