Advanced pattern matching
The basics introduced constructor cases and wildcards. More expressive patterns can match nested structure directly while preserving exhaustive, ordered behavior.
Nested constructor patterns
Section titled “Nested constructor patterns”datatype Tree Leaf(Int) Pair(Tree, Tree)end
func leftmost(tree : Tree) -> Int do switch tree case Tree::Leaf(value) then value case Tree::Pair(Tree::Leaf(value), _) then value case Tree::Pair(left, _) then leftmost(left) endendThe target is evaluated once. Pattern projections do not re-evaluate nested payloads.
Record and tuple patterns
Section titled “Record and tuple patterns”A record pattern without .. is exact. Add .. to ignore additional fields:
type Point3 = {x: Int, y: Int, z: Int}
func horizontal(point : Point3) -> (Int, Int) do switch point case {x, y, ..} then (x, y) endendTuple patterns are exact numbered-record patterns:
func choose(pair : (Bool, Int)) -> Int do switch pair case (true, value) then value case (false, _) then 0 endendAlias patterns
Section titled “Alias patterns”name @ pattern binds the complete value as well as its parts:
datatype Token Number(Int) Word(String)end
func keep_number(token : Token) -> Token do switch token case whole @ Token::Number(_) then whole case Token::Word(_) then Token::Number(0) endendIn a local pattern binding, the root alias appears before @, such as
whole @ (left, right) = pair;. That binding form must be exhaustive.
Or-patterns
Section titled “Or-patterns”Alternatives use | and share one case body:
func is_small(value : Int) -> Bool do switch value case 0 | 1 | 2 then true case _ then false endendEvery alternative must bind exactly the same names with the same types. This prevents the shared guard and body from receiving different environments.
Guards
Section titled “Guards”A when guard runs after the pattern matches and its bindings are available:
func compare_pair(pair : (Int, Int)) -> String do switch pair case (left, right) when left < right then "ascending" case (left, right) when left > right then "descending" case _ then "equal" endendCases are tried in source order. A false guard continues with the next case against the same already-evaluated target. Guarded cases do not count toward exhaustiveness because the guard might be false; an unguarded fallback is therefore still required.
Array patterns are intentionally not documented here while their implemented behavior and the draft specification are being reconciled.
View constructor patterns
Section titled “View constructor patterns”A view pattern matches a value through a second datatype. The target can be
opaque or host-backed; an associated IView provider converts it to a view
value, then ordinary constructor, record, tuple, and nested patterns inspect
that value.
datatype SignalView extensions SignalViewForString Empty Text(String) Number(Int)end
extension SignalViewForString : IView for SignalView type Target = String func view(value : String) -> SignalView do if value == "" then SignalView::Empty elsif value == "42" then SignalView::Number(42) else SignalView::Text(value) end endend
func describe(value : String) -> String do switch value case SignalView::Empty then "empty" case SignalView::Number(number) then "number " ++ number.to_string() case SignalView::Text(text) then "text " ++ text endendThe compiler finds the IView for SignalView provider whose associated
Target is String, inserts one view call, and checks exhaustiveness against
the view constructors. A view constructor pattern is distinct from an
ordinary constructor pattern: if the switch target already has type
SignalView, SignalView::Text(text) does not call the view provider.
An alias at the view pattern binds the original target, not the temporary view value:
func keep_original(value : String) -> String do switch value case original @ SignalView::Number(_) then original case _ then value endendView patterns compose with ordinary nested patterns. Each nested view is resolved from the type at its own pattern position. A view is declared to be total, so wildcard and binder cases already match the original target and do not need a conversion. When constructor cases need the view, one conversion is shared across the switch. All constructor cases in one switch must belong to the same matching space.
The provider is found through associated-extension rules. The view datatype
must associate its provider, and a provider’s Target must be unique within
that view datatype. The JSON chapter uses this feature for
opaque host-backed values.