Source guide/pattern-matching.simd

1# Pattern Matching
2
3`:match` appeared briefly in [chapter 3](./03-control-flow.simd).
4This chapter covers the full pattern grammar, the mechanics of
5:gloss[exhaustiveness](./A1-glossary.simd#exhaustiveness) checking,
6and two features that make Silo's matcher pull more weight than
7most: :gloss[Never](./A1-glossary.simd#never)-elimination isomorphisms
8and downcasting on
9:gloss[existential types](./A1-glossary.simd#existential-type).
10
11## The shape of `:match`
12
13`:match` pops the top value and runs the first arm whose pattern
14matches. Each arm is `| pattern => body`. The arms run in source
15order; the compiler checks that the arms together cover every
16value the scrutinee can take.
17
18A worked example:
19
20```silo
21:union Expr
22  | Lit Int
23  | Add Expr Expr
24  | Mul Expr Expr
25:end
26
27:fn eval ( Expr -> Int )
28  :match
29    | Lit n =>
30      n
31    | Add a b =>
32      a eval b eval +
33    | Mul a b =>
34      a eval b eval *
35  :end
36:end
37```
38
39Each arm pops the `Expr`, binds the payload to names, runs its
40body, and leaves one `Int` on the stack. The compiler checks that
41every variant is covered and that every arm produces the same
42stack shape.
43
44## Pattern forms
45
46The grammar for what appears between `|` and `=>`:
47
48| Form                          | Example                         | Matches                                   |
49|-------------------------------|---------------------------------|-------------------------------------------|
50| Union variant with payload    | `Lit n`                         | `Lit` variants, binding the payload       |
51| Named-field destructure       | `Rect .width w .height h`       | Union variants with named fields, bound by name |
52| Record destructure            | `Point .x px .y py`             | A record's fields, bound by name          |
53| Integer literal               | `42`                            | That specific integer                     |
54| String literal                | `"hello"`                       | That specific string                      |
55| Boolean literal               | `true`                          | That specific Bool                        |
56| Range                         | `0..10`                         | Any integer in the half-open range        |
57| Symbol literal                | `'ok`                           | That specific symbol                      |
58| Type name (existential)       | `Int`                           | Downcasts an existential to a type        |
59| Wildcard                      | `_`                             | Any value, ignoring it                    |
60
61Patterns nest. You can destructure a union variant whose payload
62is another union, and so on as deep as you like:
63
64```silo
65:match
66  | Some (Ok n) =>
67    n "got {}" format
68  | Some (Err e) =>
69    e "error: {}" format
70  | None =>
71    "nothing"
72:end
73```
74
75## Exhaustiveness
76
77The compiler checks that every possible value of the scrutinee is
78covered. If any case could reach `:end` without matching, that's a
79compile error — not a runtime one.
80
81```silo
82# This does NOT compile:
83:fn flaky ( (Option Int) -> Int )
84  :match
85    | Some n => n
86    # Missing | None => ...
87  :end
88:end
89```
90
91The error points at the missing arm. To suppress the check
92deliberately — for example, when you've proven elsewhere that a
93case can't happen — use `_`:
94
95```silo
96:match
97  | Some n => n
98  | _      => 0
99:end
100```
101
102A `_` arm always matches, so combining it with any other patterns
103is exhaustive by construction.
104
105## `:if-let` and `:let` revisited
106
107The single-pattern variants from
108[chapter 3](./03-control-flow.simd) desugar into a two-arm
109`:match`. Exhaustiveness applies to both:
110
111- `:if-let PAT => body :else else-body :end` desugars to
112  `:match | PAT => body | _ => else-body :end`. The `_` arm
113  covers everything `PAT` doesn't, so the whole thing is
114  exhaustive regardless of which variant `PAT` picks.
115- `:let PAT :else else-body :end` does the same, but the
116  `:else` branch must diverge — the compiler checks that it
117  cannot fall through, because the rest of the enclosing word
118  depends on `PAT`'s bindings having been produced.
119
120## Never in return types
121
122[Chapter 6](./06-types.simd#unit-and-never) introduced `Never`
123as the bottom type. The pattern matcher interacts with `Never`
124in two important ways.
125
126**Uninhabitable arms may be omitted.** When a union variant's
127payload contains `Never`, that variant has no values, and the
128compiler lets you leave out its arm:
129
130```silo
131:fn safe-parse ( Str -> (Result Int Never) ) ... :end
132
133"42" safe-parse :match
134  | Ok n =>
135    n                          # only Ok arm needed — exhaustive
136:end
137```
138
139`(Result Int Never)` has only `Ok` values at runtime; `Err`
140would need a `Never` value, which can't exist. The compiler
141recognises this and drops the exhaustiveness requirement on the
142`Err` arm.
143
144**Safe unwrap when failure is impossible.** The same reasoning
145lets `.unwrap` avoid declaring `+Panic`:
146
147```silo
148"42" safe-parse .unwrap        # ( (Result Int Never) -> Int )
149```
150
151The implementation of `.unwrap` panics on `Err`, but on a value
152of type `(Result Int Never)` there *are* no `Err` values — so
153the panic is unreachable, and the effect row can be empty.
154
155## Type-level isomorphisms via `=>`
156
157When you see a return type like `(Result Int Never)` pop up a
158lot, a function's signature can declare the isomorphism
159explicitly with `=>`:
160
161```silo
162:fn safe-parse2 ( Str -> (Result Int Never) => Int ) ... :end
163```
164
165Reading the signature: the word returns `(Result Int Never)`,
166which is isomorphic to `Int`. Callers can treat the result as
167either shape interchangeably:
168
169```silo
170"42" safe-parse2 1 +           # coerced to Int automatically
171"42" safe-parse2 .unwrap       # still works as Result too
172```
173
174The recognised isomorphisms are:
175
176| Type                  | Isomorphic to |
177|-----------------------|---------------|
178| `(Result a Never)`    | `a`           |
179| `(Result Never b)`    | `b`           |
180| `(Option Never)`      | `Unit`        |
181
182Each corresponds to a union variant being uninhabitable. These
183are not magic; the compiler verifies each case by checking that
184the excluded variant's payload is inhabited only by `Never`.
185
186## Downcasting existential types
187
188A trait name in a type position is an **existential type**: "some
189value implementing this trait, concrete type unknown". Pattern
190matching on an existential lets you **downcast** — check what
191concrete type is inside and bind accordingly:
192
193```silo
194:fn describe ( Display -> Str )
195  :match
196    | Int => "{}" format " (integer)" +
197    | Str => "\"" swap + "\" (string)" +
198    | _   => "{}" format " (other)" +
199  :end
200:end
201```
202
203The arm patterns here are type names, not values. `| Int =>` says
204"if the existential's concrete type is `Int`, run this arm". The
205compiler checks that each type you mention actually implements
206the existential's trait; `Int =>` on a `(Display …)` is fine
207because `Int` implements `Display`, but `Widget =>` would fail if
208`Widget` doesn't.
209
210Downcasting is useful exactly where you'd use `match` on a trait
211object in Rust or a type-case in a language with explicit runtime
212types. The wildcard `_` arm handles everything not enumerated —
213including trait-implementors the compiler hasn't seen yet in
214other modules.
215
216## When to reach for `:match`
217
218Most code you write won't need `:match`. Silo's other control
219forms — `:if`, `:if-let`, `:let`, `:try` — are shorter and
220clearer when they fit. Reach for a full `:match` when:
221
222- You have more than two cases to distinguish.
223- The cases have structurally different shapes (payload
224  destructuring).
225- You genuinely need exhaustiveness on a union or enum with
226  many variants.
227- You're downcasting an existential.
228
229The Silo idiom is to let the simplest form that still reads
230well carry the weight.
231
232## Key points
233
234- `:match` arms are pattern → body, checked for exhaustiveness.
235- The pattern grammar covers variants, records, literals,
236  ranges, symbols, type names (for existential downcast), and
237  `_`. Patterns nest.
238- `:if-let` and `:let` are two-arm `:match`s in disguise; the
239  exhaustiveness story carries through.
240- `Never` in a union variant's payload makes the variant
241  uninhabitable. The compiler lets you omit its arm and lets
242  operations like `.unwrap` skip `+Panic`.
243- `=>` in a signature declares a `Never`-elimination
244  isomorphism. Callers can use either the original type or the
245  isomorphic one.
246- Pattern matching on a trait-typed existential downcasts to
247  concrete types, with `_` catching everything not listed.
248
249Next: [traits](./09-traits.simd) — the system you've been
250seeing referenced throughout.