Skip to content
Noodle
InstallLearnPlayground
GitHub

Advanced pattern matching

The basics introduced constructor cases and wildcards. More expressive patterns can match nested structure directly while preserving exhaustive, ordered behavior.

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)
end
end

The target is evaluated once. Pattern projections do not re-evaluate nested payloads.

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)
end
end

Tuple patterns are exact numbered-record patterns:

func choose(pair : (Bool, Int)) -> Int do
switch pair
case (true, value) then value
case (false, _) then 0
end
end

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)
end
end

In a local pattern binding, the root alias appears before @, such as whole @ (left, right) = pair;. That binding form must be exhaustive.

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
end
end

Every alternative must bind exactly the same names with the same types. This prevents the shared guard and body from receiving different environments.

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"
end
end

Cases 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.

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
end
end
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
end
end

The 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
end
end

View 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.

Next: View patterns in the JSON chapter.