Companion film
Name the logic. Close the state.
Change one order total and watch a named pricing function and a closed Order choice update the decision while the ledger stays intact.
49 secGrid 0.63.2
Open film page →Canonical Grid model
Payload-bearing CHOICE types: constructors, exhaustive constructor-pattern MATCH, guards, and a finite recursive value.
What this model gives you
20 min to study · Source reviewed 2026-08-25
Continue with guided practiceExpected checkpoint
After constructing the canonical order states and two-hop approval escalation.
Draft, placed, shipped, and cancelled values carry only their declared payloads.
Every constructor has an unguarded path while guards refine policy inside one case.
Nested escalation constructors retain the complete approval path without an unbounded evaluator.
MODEL "Algebraic Choice Lifecycle"
DESCRIPTION "Payload-bearing CHOICE types: constructors, exhaustive constructor-pattern MATCH, guards, and a finite recursive value."
VERSION "1.0.0"
AUTHOR "Grid Team"
TAGS "canonical", "choice", "adt", "match", "recursion"
# A closed order lifecycle with payload-bearing constructors
CHOICE Order =
Draft |
Placed(total: number) |
Shipped(total: number, transit_days: number) |
Cancelled(reason: string)
# Constructed values are immutable and contract-checked at build time
A1 = Order.Placed(1840)
A2 = Order.Shipped(2620, 3)
A3 = Order.Cancelled("payment declined")
A4 = Order.Draft()
# Exhaustive matching: every constructor needs an unguarded arm
B1 = MATCH(
A2,
Order.Draft() -> "draft",
Order.Placed(total) -> "placed " & TEXT(total, "0"),
Order.Shipped(total, transit_days) -> "shipped " & TEXT(transit_days, "0") & "d",
Order.Cancelled(reason) -> "cancelled: " & reason
)
# Guards test payload values; the unguarded arms keep the match exhaustive
B2 = MATCH(
A1,
Order.Placed(total) IF total > 1500 -> "priority-pick",
Order.Placed(_) -> "standard-pick",
Order.Shipped(_, _) -> "in-transit",
_ -> "no-pick"
)
# Payload extraction with ignored fields
B3 = MATCH(
A2,
Order.Shipped(total, _) -> total,
_ -> 0
)
# A finite recursive value: an escalation chain stored as data
CHOICE Approval =
Granted(approver: string) |
Escalated(role: string, remainder: Approval)
C1 = Approval.Escalated("team-lead", Approval.Escalated("director", Approval.Granted("finance-chief")))
# Fold the chain with nested matches; recursive values stay finite (depth <= 64)
D1 = MATCH(
C1,
Approval.Granted(approver) -> approver,
Approval.Escalated(role, rest) -> MATCH(
rest,
Approval.Granted(mid_approver) -> mid_approver,
Approval.Escalated(mid_role, deeper) -> MATCH(
deeper,
Approval.Granted(top_approver) -> top_approver,
_ -> "still-escalating"
)
)
)
D2 = MATCH(
C1,
Approval.Granted(_) -> 0,
Approval.Escalated(_, rest) -> 1 + MATCH(
rest,
Approval.Granted(_) -> 0,
Approval.Escalated(_, deeper) -> 1 + MATCH(deeper, Approval.Granted(_) -> 0, _ -> 1)
)
)
# Summary
E1 = `final_approver={D1} escalation_hops={D2} pick={B2}`
END MODEL