Pattern matching in Osprey uses field name matching only. Record type patterns are based on structural equivalence by field names, not field order. See Type System for complete type unification rules.
let result = match value {
0 => "zero"
1 => "one"
n => "other: " + toString(n)
}
type Option = Some { value: Int } | None
let message = match option {
Some x => "Value: " + toString(x.value)
None => "No value"
}
The underscore _ matches any value:
let category = match score {
100 => "perfect"
90 => "excellent"
_ => "good"
}
Type annotation patterns use the : operator to match values of specific types. This is REQUIRED for any types.
type_pattern := ID ':' type
structural_pattern := ID ':' '{' field_list '}'
anonymous_structural_pattern := '{' field_list '}'
constructor_pattern := ID ('(' pattern (',' pattern)* ')')?
variable_pattern := ID
wildcard_pattern := '_'
Examples:
// Required for any types
match anyValue {
num: Int => num + 1
text: String => length(text)
flag: Bool => if flag then 1 else 0
_ => 0
}
// Structural matching - matches any type with these fields
match anyValue {
{ name, age } => print("${name}: ${age}") // Anonymous structural
p: { name, age } => print("Person ${p.name}: ${p.age}") // Named structural
u: User { id } => print("User ${id}") // Traditional typed
_ => print("Unknown")
}
// Advanced structural patterns
match anyValue {
{ x, y } => print("Point: (${x}, ${y})") // Any type with x, y fields
p: { name } => print("Named thing: ${p.name}") // Any type with name field
{ id, email, active: Bool } => print("Active user: ${id}") // Mixed field patterns
_ => print("No match")
}
// Type patterns with field destructuring
match result {
success: Success { value, timestamp } => processSuccess(value, timestamp)
error: Error { code, message } => handleError(code, message)
_ => defaultHandler()
}
match anyValue {
i: Int => i * 2 // Bind as 'i' if Int
s: String => s + "!" // Bind as 's' if String
user: User => user.name // Bind as 'user' if User type
}
Match on structure without requiring specific type names:
match anyValue {
{ name, age } => print("${name} is ${age}") // ANY type with name, age
{ x, y, z } => print("3D point: ${x},${y},${z}") // ANY type with x, y, z
{ id } => print("Has ID: ${id}") // ANY type with id field
}
Bind the whole object AND destructure fields:
match anyValue {
person: { name, age } => {
print("Person: ${person}") // Access whole object
print("Name: ${name}") // Access destructured field
print("Age: ${age}") // Access destructured field
}
point: { x, y } => calculateDistance(point, origin)
}
match anyValue {
user: User { id, name } => print("User ${id}: ${name}") // Explicit type
{ email, active } => print("Has email: ${email}") // Structural only
data: { values: Array<Int> } => processArray(data.values) // Nested types
_ => print("Unknown structure")
}
All arithmetic expressions return Result<T, MathError> and must be handled with pattern matching. The compiler automatically unwraps intermediate Results in nested arithmetic, so only the final result requires pattern matching.
✨ KEY INSIGHT: Nested arithmetic expressions do NOT require nested pattern matching! The compiler automatically unwraps intermediate Results, so you only pattern match the final result.
let calculation = 1 + 3 + (300 / 5) // Result<int, MathError>
match calculation {
Success { value } => print("Result: ${value}")
Error { message } => print("Math error: ${message}")
}
Nested arithmetic expressions return a single Result, not nested Results:
let simple = 10 + 5 // Result<int, MathError>
let complex = 1 + 2 * 3 - 4 / 2 // Result<int, MathError>
let nested = ((a + b) * c) / (d - e) // Result<int, MathError>
// All handled the same way
match simple {
Success { value } => print("Result: ${value}")
Error { message } => print("Error: ${message}")
}
The compiler auto-unwraps intermediate Results in arithmetic chains:
let example = (10 + 5) * 2 // Single Result<int, MathError>
match example {
Success { value } => print(value) // 30
Error { message } => print(message)
}
let x = 10 + 5 // Result<int, MathError>
print(x) // Prints: Success(15)
let y = 10 / 0 // Result<float, MathError>
print(y) // Prints: Error(DivisionByZero)
// Success format: Success(value)
// Error format: Error(message)
fn calculate(x: int, y: int) -> Result<int, MathError> = x + y * 2 - 5
let result = calculate(10, 3) // Result<int, MathError>
match result {
Success { value } => print("Function result: ${value}")
Error { message } => print("Function failed: ${message}")
}
// Multiple Results in sequence
let step1 = 100 + 50 // Result<int, MathError>
let step2 = 200 * 3 // Result<int, MathError>
// Handle each step
match step1 {
Success { value1 } => {
match step2 {
Success { value2 } => {
let final = value1 + value2 // This is also Result<int, MathError>!
match final {
Success { total } => print("Final: ${total}")
Error { message } => print("Final calc failed: ${message}")
}
}
Error { message } => print("Step 2 failed: ${message}")
}
}
Error { message } => print("Step 1 failed: ${message}")
}
## Ternary Match Expression (Syntactic Sugar)
To reduce verbosity for common two-armed match scenarios, Osprey provides a concise ternary match expression. This is **purely syntactic sugar** and desugars to a standard `match` expression internally.
**Syntax:**
`<expression> { <pattern> } ? <then_expr> : <else_expr>`
This is exactly equivalent to:
```osprey
match <expression> {
{ <pattern> } => <then_expr>
_ => <else_expr>
}
<expression>: The value to be matched.{ <pattern> }: A structural pattern to match against the expression. This can be used for destructuring.? <then_expr>: The expression to evaluate if the pattern matches.: <else_expr>: The expression to evaluate if the pattern does not match.
Example 1: Handling Built-in Result Types
The built-in Result<T, E> type uses Success { value: T } and Error { message: E } constructors.
// Arithmetic operations return Result<int, MathError>
let calculation = 10 + 5 // Result<int, MathError>
// Extracts value using the structural ternary match
let message = calculation { value } ? value : -1
// message is now 15
// Function returning Result type
fn divide(a: int, b: int) -> Result<int, MathError> = a / b
let result = divide(a: 10, b: 2)
let safeValue = result { value } ? value : 0
// safeValue is now 5
Example 2: Handling Booleans
The ternary match can also work with boolean values by matching on the implicit structure of a true value.
let is_active = true
let status_text = is_active ? "Active" : "Inactive"
// status_text is now "Active"
Example 3: Handling Result Types with Ternary
For Result types, the ternary operator provides a clean way to extract success values or provide defaults:
// Arithmetic operations return Result<int, MathError>
let calculation = 10 + 5 // Result<int, MathError>
// Result ternary: automatically extracts Success value or uses default on Error
let value = calculation ?: -1 // 15 (extracts value from Success) or -1 (on Error)
// Function returning Result type
fn divide(a: int, b: int) -> Result<int, MathError> = a / b
let goodResult = divide(a: 10, b: 2)
let safeValue = goodResult ?: -1 // 5 (extracts value from Success) or -1 (on Error)
let badResult = divide(a: 10, b: 0)
let errorValue = badResult ?: -1 // -1 (Error case, uses default)
This is syntactic sugar for:
let value = match calculation {
Success { value } => value
Error { message } => -1
}