Pattern Matching Arrives in Luma: Match Your Way to Cleaner Code
If you’ve written more than a few dozen lines of Luma, you’ve written an if/else chain you weren’t proud of. Something like this:
if status == "active" {
print("user is active")
} else if status == "banned" {
print("user is banned")
} else if status == "pending" {
print("waiting for approval")
} else {
print("unknown status")
}Four branches. The variable status repeated four times. The logic is simple, but the code makes it look complicated.
Today that changes. Pattern matching is here.
match status {
"active" -> print("user is active")
"banned" -> print("user is banned")
"pending" -> print("waiting for approval")
_ -> print("unknown status")
}Same logic, half the noise. The subject is stated once. Each arm is a pattern followed by -> and a body. The _ wildcard catches everything else.
This is Phase 1 of Luma’s pattern matching — value matching, variable binding, guard clauses, optional unwrapping, block bodies, and match as an expression. All shipping today.
Why pattern matching matters
Pattern matching isn’t just syntax sugar over if/else. It changes how you think about branching.
With if/else, you’re writing conditions. Each branch is a boolean expression, and you’re responsible for making sure they’re mutually exclusive and exhaustive. The compiler doesn’t help much — it just runs them top to bottom.
With match, you’re writing patterns. Each arm declares what shape the data should have, and the runtime finds the first match. You focus on describing values, not constructing predicates.
This matters most when the number of cases grows. Three if/else branches are fine. Eight are a mess. With match, eight arms are still readable — because each one is self-contained and the subject is never repeated.
Match is an expression
One of the most useful properties of match is that it produces a value. You can assign the result directly:
label: str = match code {
200 -> "OK"
404 -> "Not Found"
500 -> "Server Error"
_ -> "Unknown"
}
print(label)No temporary variable. No label = "..." inside each branch. The match evaluates to the value of the matching arm, and that value flows into the declaration.
This is a pattern you’ll use constantly — transforming one value into another based on a fixed set of cases.
Variable binding
When a pattern is an identifier instead of a literal, it binds the matched value to that name:
match value {
0 -> print("zero")
1 -> print("one")
n -> print("other: ${n}")
}The arm n -> ... matches anything and makes the value available as n. This is a catch-all that gives you access to the value, unlike _ which discards it.
Binding is the bridge between matching and doing something with the result.
Guard clauses
Sometimes the pattern alone isn’t enough. Guards add a condition using if:
match score {
n if n >= 90 -> print("Grade: A")
n if n >= 80 -> print("Grade: B")
n if n >= 70 -> print("Grade: C")
_ -> print("Grade: F")
}The variable n is bound to the subject, and the guard is checked. If the guard fails, matching continues to the next arm. This keeps patterns simple — the pattern matches structure, the guard handles logic.
Compare this with the if/else version:
if score >= 90 {
print("Grade: A")
} else if score >= 80 {
print("Grade: B")
} else if score >= 70 {
print("Grade: C")
} else {
print("Grade: F")
}The match version is shorter, but more importantly, its intent is clearer — you’re classifying a score, not evaluating a chain of conditions.
Optional unwrapping
Matching on nil vs a value is one of the most practical uses:
name: ?str = nil
match name {
nil -> print("No name provided")
val -> print("Hello, ${val}!")
}The nil pattern matches absence. The val pattern binds the unwrapped value. No manual nil checks, no if name != nil boilerplate.
This pattern appears everywhere — function results, configuration values, user input. Match makes it clean every time.
Block bodies
When an arm needs more than a single expression, use { }:
match command {
"greet" -> {
print("Hello!")
print("Welcome to Luma")
}
"bye" -> {
print("Goodbye!")
print("See you soon")
}
_ -> print("Unknown command")
}Block bodies work exactly like lambda block bodies. You can put variable declarations, function calls, and any other statements inside them.
How it compiles
Luma compiles to Go, and the match compilation strategy is straightforward.
Simple literal patterns produce a Go switch statement:
switch _match_subj := status; _match_subj {
case "active":
fmt.Println("user is active")
case "banned":
fmt.Println("user is banned")
default:
fmt.Println("unknown status")
}Clean, efficient, and exactly what you’d write by hand in Go.
Guards and bindings produce an if/else chain instead, since Go’s switch can’t express arbitrary conditions:
_match_subj := score
if _match_subj >= 90 {
fmt.Println("Grade: A")
} else if _match_subj >= 80 {
fmt.Println("Grade: B")
} else {
fmt.Println("Grade: F")
}Match as expression wraps the logic in an immediately-invoked function so it can return a value:
func() interface{} {
_match_subj := code
switch _match_subj {
case 200: return "OK"
case 404: return "Not Found"
default: return "Unknown"
}
return nil
}()The compiler picks the simplest strategy for each case. You don’t need to think about any of this — but if you’re curious about what your Luma produces, it’s always readable Go on the other side.
What we shipped
Phase 1 covers the features that matter most for everyday code:
| Feature | Status |
|---|---|
| Value matching (strings, ints, bools) | Done |
Wildcard (_) |
Done |
| Variable binding | Done |
Guard clauses (if) |
Done |
nil matching |
Done |
| Match as expression | Done |
| Block bodies | Done |
All six playground examples are available in the Luma Playground under the “Match:” prefix.
What’s coming next
Phase 1 is the foundation. Here’s what we’re planning:
Phase 2: Type matching. Match on concrete types behind a trait interface — c: Circle -> ... instead of if shape.is_type("Circle"). This is the big one for polymorphic code.
Phase 3: Enums. Named variants with optional associated data, plus exhaustiveness checking. When you add a new variant, the compiler tells you every match that needs updating.
Future: Multiple patterns per arm. "Saturday" | "Sunday" -> "weekend" — matching several values without duplicating the body.
These are planned, not promised. We’ll ship them when they’re ready and when the design feels right. But the foundation is solid, and we’re confident about the direction.
Try it
Pattern matching is available now. Update your Luma installation, open the playground, and start replacing those if/else chains.
match status {
"active" -> "Active User"
"banned" -> "Banned"
_ -> "Unknown"
}Shorter. Clearer. Luma.