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.