A Denotational Semantics for Constraints
This note gives a denotational semantics for GoFish's layout constraints, in the style of Spytial's Table 1 (Prasad et al., Patterns for Spatial Reasoning): each construct is given a meaning
The payoff of writing it down: the meaning of a layer is the composition of its constraints' denotations, and because each denotation lives in a semiring closed under composition, the composite is always well-defined and invertible. That is the completeness conjecture of layout-synthesis, stated semantically.
Semantic domains
A layout assigns every node
- an extent
— its size; - a position
— where its origin sits.
Neither is a free number. Extents are claims: monotone functions of a scale factor
A fixed box denotes the constant claim
So the meaning of a node splits in two, matching the two passes:
The two semirings
Composition happens in two tropical semirings, one per quantity (see layout-synthesis Part 2):
- Sizes compose in
over : series (stacking) is , overlay (alignment) is , constants are , data is . Closed under composition, so any network folds to a single — hence invertible. - Positions compose in
: a system of difference constraints is a shortest-path problem; on a forest (one anchor, no alternative paths) the positions are just path sums from the anchor.
The layer is the solver
A constraint is meaningless in isolation — it denotes a contribution to its enclosing layer
and evaluates: every node's extent is Monotonic.inverse; the monotonic module is the implementation of
The denotations
Below, a constraint relates a layer's ordered children start: end: middle: baseline:
distribute (series on axis )
A series: claims add (plus the spacing constant), and positions are a chain of difference constraints — the running-sum walk.
align (overlay on axis )
for a single shared baseline
position (a pin)
A literal pins to a pixel; a datum
nest (unary, padding )
A unary
grid (the symmetric 2-D layout)
For cells constraints/grid.ts):
This is the grid constraint owns. The axes additionally denote
v1 note. The implementation specializes the equal-track case: every cell fills its track, so
and the equation collapses to , solved by box-division ( sliceExtent). The general-of-max (content-sized tracks) and the symbolic flex claim — so this prints as — arrive with flex-as-datum (the spread fill story).
overlay (a bare layer) and z-order
A layer with no size-folding constraint denotes pure overlay:
zAbove / zBelow denote only a paint order — a relation
Table 1: the constraints at a glance
| constraint | size denotation | placement denotation | role |
|---|---|---|---|
distribute | series | ||
align | overlay | ||
position(v) | datum domain | anchor / pin | |
nest(o,i) | unary | ||
grid | cell centered in its | symmetric 2-D | |
layer (bare) | children at own positions | overlay | |
zAbove/zBelow | — | — (paint order | z-order |
Why the composition is total
Read the table column-wise. Every size denotation is built from
That is the completeness statement of constraints-as-core made precise:
The denotation of a layer of
over children with monotone claims is a claim on the size side and a difference-constraint forest on the position side — hence always solvable, and closed under nesting.
Operators are then just notations for particular denotations: z-order (a render-order relation, no geometry) and custom algorithmic layouts like treemap (arbitrary computation that emits claims, pins, and placements under the same denotations).
