Protocol-First Matching and Case Selection
Canonical source:
docs/guide/12-matching-expressions.mdat locked revisione99b712ecf658a527b1fe82c8beba2c1286d8dc9. This rendered page is non-normative; the applicable Protos specification remains authoritative.
Status: Non-normative programming guide
Primary normative owners:
spec/semantics/MATCHING.mdandspec/PROTOS_GRAMMAR.mdCurrent implementation track:
I041 — D131 protocol-first matching implementation
Matching in current Protos is deliberately small: matching is an ordinary protocol, not a dedicated language construct.
The core idea is:
pattern.match(subject)The matcher is the receiver. The subject is the argument.
There is no postfix subject match { ... } expression, no case grammar, no
matching-only binders, wildcard, guards, OR-pattern institution, exact/remainder
pattern syntax, or captures(...) source form in Core v0.1.
The words match, case, when, exact, and captures are ordinary names.
The root matcher protocol
Section titled “The root matcher protocol”Every ordinary object inherits the standard root behavior:
Object.match(subject) -> this == subjectObject.match performs exactly one ordinary equality send with the matcher as
the equality receiver and returns that result unchanged.
For ordinary values this means:
42.match(42)"ready".match(status)uses the same customizable equality model as ordinary ==. Matching does not
introduce a second equality relation.
A domain object may define or override match(subject) like any other method:
Positive: { match: (subject) => subject > 0}
Positive.match(12)No registry, type declaration, pattern class, or compiler hook is required.
Matcher results
Section titled “Matcher results”A matching consumer recognizes exactly these normal outcomes:
false mismatchtrue success with zero captures[one, or, more, values] success with positional capturesThe capture carrier must be a non-empty standard Array.
An empty Array is not another spelling for zero-capture success. Other normally
returned values such as null, Numbers, Strings, arbitrary objects, or Futures
are invalid matcher outcomes when consumed by structural matching or caseOf
and signal ordinary Error.
Captured Arrays remain one captured value; capture composition does not recursively flatten nested collections.
Any and Capture
Section titled “Any and Capture”Core initially provides exactly two standard helper matchers:
Any.match(subject)Capture.match(subject)Any.match(subject) always returns canonical true.
Capture.match(subject) returns [subject], preserving the exact subject
reference.
Any and Capture are ordinary standard objects, not keywords.
Structural Array matching
Section titled “Structural Array matching”A standard Array is itself a structural matcher:
matcher: [1, Capture, Any]subject: [1, "payload", 99]
result: matcher.match(subject)Array.match is exact-length matching.
For one attempt, Protos shallowly observes both matcher and subject Arrays before invoking any child matcher. Reached child matchers run exactly once from left to right:
matcherElement.match(subjectElement)A child false fails immediately. true contributes no capture. A non-empty
standard Array contributes its outer elements to the positional capture
sequence.
If no child contributes captures, the result is canonical true; otherwise the
result is one new standard Array containing captures in child order.
Core v0.1 defines no Array remainder matcher.
Structural Map matching
Section titled “Structural Map matching”A normal standard Map is also a structural matcher:
matcher: Map()matcher["kind"] = "data"matcher["payload"] = Capture
subject: Map()subject["kind"] = "data"subject["payload"] = 42subject["extra"] = "ignored"
result: matcher.match(subject)Map.match is open/subset matching. Every matcher association must resolve
and match, while unrelated subject associations are ignored.
For one attempt, Protos establishes stable shallow observations of matcher requirements and subject associations. Requirements are processed in matcher insertion order.
Key selection uses the ordinary normal-Map law:
- evaluate the query key’s current
hash; - consider same-hash subject associations in subject insertion order;
- send query-side
queryKey == storedRepresentativeKey; - select the first canonical-
trueresult.
Key objects are not themselves interpreted as matchers during association lookup.
All required subject associations are resolved and their mapped-value references fixed before the first nested value matcher runs. Mapped-value matchers then run in matcher insertion order.
Missing required associations yield canonical false. Child matcher outcomes
compose captures through the same false / true / non-empty-Array protocol.
Core v0.1 defines no exact-Map mode and no Map-remainder capture.
Multi-way selection with caseOf
Section titled “Multi-way selection with caseOf”Multi-way selection is an ordinary message:
value.caseOf(cases)cases is a normal standard Map whose keys are matchers and whose values are
callables:
cases: Map()
cases["ready"] = () => "can run"cases[Capture] = (other) => other
status.caseOf(cases)The cases Map is shallowly observed in insertion order when the attempt starts. Later mutation does not rewrite that attempt.
Each reached matcher is asked once:
matcher.match(value)The result controls selection:
false continuetrue action()[captures] action(...captures)The first success commits. Unreached matchers and actions are not eagerly validated or invoked.
The selected callable’s result is returned unchanged, including null. If no
case succeeds, caseOf signals one fresh ordinary Error.
Because the carrier is an ordinary normal Map, its normal unique-key, hash/equality, and insertion-order semantics are part of the model.
Guard-like behavior needs no special syntax
Section titled “Guard-like behavior needs no special syntax”Core v0.1 has no matching-specific when syntax. A matcher simply implements the
predicate it needs:
Positive: { match: (subject) => subject > 0}A matcher that needs to capture after checking a condition can return a
non-empty Array on success and false otherwise. The protocol remains
matcher.match(subject); no separate guard phase is required.
Matching remains ordinary Protos
Section titled “Matching remains ordinary Protos”Useful boundaries:
- matcher authority is on the receiver of
match; - equality remains ordinary
==; - Array and Map structural behavior belongs to those standard collection protocols;
caseOfis an ordinary Object message using an ordinary Map carrier;- Errors, effects, control transfer, and suspension follow ordinary execution;
- removed pre-D131 pattern syntax is not dormant compatibility syntax.
Removed pre-D131 surface
Section titled “Removed pre-D131 surface”Older development revisions experimented with a dedicated matching language, including:
subject match { ... }case ...when ...@name_Array/Map pattern grammarArray/Map remaindersexact Map patternsOR patternscaptures(...)Those forms are intentionally not part of current Core v0.1. D131/I041 removed them rather than retaining aliases or compatibility syntax.
Future syntactic sugar or higher-level matcher combinators can be considered by separate explicit decisions without changing the protocol foundation.
Practical checklist
Section titled “Practical checklist”- Identify the matcher object.
- Send
matcher.match(subject). - Expect only
false,true, or a non-empty standard Array from matcher outcomes consumed by matching. - Use
Capturefor one positional capture. - Use
Anyfor unconditional zero-capture success. - Use Arrays for exact-length structural matching.
- Use Maps for open/subset structural matching.
- Use
value.caseOf(cases)for ordered multi-way selection. - Remember that Map keys still follow ordinary Map
hash/==semantics. - Treat
caseOfno-selection as Error unless a case explicitly succeeds.
Implementation-backed examples
Section titled “Implementation-backed examples”Representative retained evidence includes:
ProtosObjectMatchProtocolTest;ProtosArrayMatchProtocolTest;ProtosMapMatchProtocolTest;ProtosCaseOfProtocolTest;protos/tests/conformance/matching/;ProtosParserMatchTest, which retains the rejection regression for the removed dedicated syntax.
These files are implementation evidence, not a second specification. If they disagree with the normative specification, the specification remains authoritative.