Match statements#
Code Example
Runnable Example in Jac and JacLib
"""Match statements: Pattern matching with all pattern types."""
# Define classes for class pattern examples
obj Point {
has x: float,
y: float;
}
obj Circle {
has center: Point,
radius: float;
}
obj Rectangle {
has width: float,
height: float;
}
with entry {
# ========================================
# LITERAL PATTERNS
# ========================================
# Integer literal patterns
status_code = 200;
match status_code {
case 200:
print("OK");
case 404:
print("Not Found");
case 500:
print("Server Error");
case _:
print("Other status");
}
# Float literal patterns
pi = 3.14;
match pi {
case 3.14:
print("Matched pi");
case 2.71:
print("Matched e");
case _:
print("Other number");
}
# String literal patterns
command = "start";
match command {
case "start":
print("Starting");
case "stop":
print("Stopping");
case "pause":
print("Pausing");
case _:
print("Unknown command");
}
# ========================================
# SINGLETON PATTERNS
# ========================================
# Boolean singleton patterns
flag = True;
match flag {
case True:
print("Flag is True");
case False:
print("Flag is False");
}
# None singleton pattern
optional_value = None;
match optional_value {
case None:
print("Value is None");
case _:
print("Value exists");
}
# ========================================
# CAPTURE PATTERNS
# ========================================
# Basic capture pattern - binds to variable
value = 42;
match value {
case x:
print(f"Captured value: {x}");
}
# Wildcard pattern (_) - matches anything without binding
day = "sunday";
match day {
case "monday":
print("It's Monday");
case _:
print("Not Monday");
}
# AS pattern - capture with explicit naming
number = 100;
match number {
case x as captured_num:
print(f"Captured as: {captured_num}");
}
# ========================================
# SEQUENCE PATTERNS
# ========================================
# Exact sequence match with list
coords = [1, 2, 3];
match coords {
case [1, 2, 3]:
print("Matched exact sequence");
case _:
print("Different sequence");
}
# Sequence with variable binding
point = [10, 20];
match point {
case [x, y]:
print(f"Point at ({x}, {y})");
}
# Star pattern (*) - captures remaining elements
numbers = [1, 2, 3, 4, 5];
match numbers {
case [first, *middle, last]:
print(f"First: {first}, middle: {middle}, last: {last}");
case [*all_items]:
print(f"All items: {all_items}");
}
# Star at different positions
data = [100, 200, 300, 400];
match data {
case [*start, 400]:
print(f"Ends with 400, start: {start}");
case [100, *rest]:
print(f"Starts with 100, rest: {rest}");
}
# Nested sequence patterns
matrix = [[1, 2], [3, 4]];
match matrix {
case [[a, b], [c, d]]:
print(f"2x2 matrix: [{a},{b}], [{c},{d}]");
}
# ========================================
# MAPPING PATTERNS
# ========================================
# Basic mapping pattern with exact keys
user = {"name": "Alice", "age": 30};
match user {
case {"name": "Alice", "age": 30}:
print("Matched exact user");
case _:
print("Different user");
}
# Mapping with value capture
person = {"id": 123, "role": "admin"};
match person {
case {"id": user_id, "role": user_role}:
print(f"User {user_id} is {user_role}");
}
# Double-star pattern (**) - captures remaining key-value pairs
config = {"host": "localhost", "port": 8080, "debug": True, "timeout": 30};
match config {
case {"host": h, "port": p, **rest}:
print(f"Server: {h}:{p}, other settings: {rest}");
}
# Nested mapping patterns
response = {"status": 200, "data": {"name": "Bob", "score": 95}};
match response {
case {"status": 200, "data": {"name": n, "score": s}}:
print(f"Success: {n} scored {s}");
}
# ========================================
# CLASS PATTERNS
# ========================================
# Basic class pattern with keyword arguments
p1 = Point(x=5.0, y=10.0);
match p1 {
case Point(x=x_val, y=y_val):
print(f"Point at ({x_val}, {y_val})");
}
# Class pattern with specific value matching
origin = Point(x=0.0, y=0.0);
match origin {
case Point(x=0.0, y=0.0):
print("Point at origin");
case Point(x=0.0, y=y):
print(f"On y-axis at {y}");
case Point(x=x, y=0.0):
print(f"On x-axis at {x}");
case _:
print("Point elsewhere");
}
# Nested class patterns
circle = Circle(center=Point(x=3.0, y=4.0), radius=5.0);
match circle {
case Circle(center=Point(x=cx, y=cy), radius=r):
print(f"Circle at ({cx}, {cy}) with radius {r}");
}
# Class pattern with AS capture
rect = Rectangle(width=10.0, height=20.0);
match rect {
case Rectangle(width=w, height=h) as captured_rect:
print(f"Rectangle {w}x{h}, object: {captured_rect}");
}
# ========================================
# OR PATTERNS
# ========================================
# Multiple alternative patterns with | (or)
code = 404;
match code {
case 200 | 201 | 204:
print("Success status");
case 400 | 401 | 403 | 404:
print("Client error");
case 500 | 502 | 503:
print("Server error");
case _:
print("Other code");
}
# ========================================
# GUARD CLAUSES (if conditions)
# ========================================
# Pattern with guard condition
age = 25;
match age {
case x if x < 18:
print("Minor");
case x if x < 65:
print("Adult");
case x:
print("Senior");
}
# Complex guard with multiple conditions
score = 85;
match score {
case s if s >= 90:
print("Grade: A");
case s if s >= 80:
print("Grade: B");
case s if s >= 70:
print("Grade: C");
case s if s >= 60:
print("Grade: D");
case _:
print("Grade: F");
}
# ========================================
# COMBINED PATTERNS
# ========================================
# Combining different pattern types
shape_data = {"type": "circle", "center": [0, 0], "radius": 10};
match shape_data {
case {"type": "circle", "center": [x, y], "radius": r}:
print(f"Circle at ({x},{y}) r={r}");
case {"type": "rectangle", "corners": [[x1, y1], [x2, y2]]}:
print(f"Rectangle from ({x1},{y1}) to ({x2},{y2})");
case _:
print("Unknown shape");
}
# Multiple statements in case block
result = "success";
match result {
case "success":
print("Operation succeeded");
status_val = 200;
print(f"Status code: {status_val}");
case "error":
print("Operation failed");
status_val = 500;
print(f"Status code: {status_val}");
case _:
print("Unknown result");
status_val = 0;
}
print("Match patterns demonstration complete");
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
|
Jac Grammar Snippet
Description
Match Statements: Comprehensive Pattern Matching
Match statements provide a powerful pattern matching construct for handling different data structures and values in a declarative way. They offer a more expressive alternative to chains of if-elif-else statements, supporting multiple pattern types including literals, singletons, captures, sequences, mappings, and class patterns.
What is Pattern Matching?
Pattern matching allows you to compare a value against different patterns and execute code based on which pattern matches. Think of it like a more powerful switch statement that can destructure data, match complex structures, and apply conditions.
Basic Match Structure
The fundamental structure consists of:
- match
keyword followed by an expression to evaluate
- One or more case
clauses defining patterns
- Optional guard clauses using if
for additional conditions
- Case bodies containing statements to execute on match
flowchart TD
A[match expression] --> B{Pattern 1 matches?}
B -->|Yes| C{Guard condition?}
C -->|True| D[Execute case body]
C -->|False| E{Pattern 2 matches?}
B -->|No| E
D --> F[Exit match]
E -->|Yes| G{Guard condition?}
G -->|True| H[Execute case body]
G -->|False| I[Try next case]
H --> F
E -->|No| I
I --> J[Continue to next case or wildcard]
Execution Model
Match statements follow these rules: 1. Sequential Evaluation: Patterns are checked from top to bottom 2. First Match Wins: Only the first matching case executes 3. Automatic Exit: After executing a case body, control exits the match 4. Variable Binding: Variables in patterns are scoped to that case body 5. Guard Filtering: Guards provide boolean filtering after pattern matching
Pattern Types Overview
Pattern Type | Syntax Example | Matches | Lines |
---|---|---|---|
Literal | case 200: |
Exact int/float/string values | 27-35, 39-47, 51-59 |
Singleton | case True: , case None: |
Boolean constants and None | 67-72, 76-81 |
Capture | case x: |
Any value, binds to variable | 89-92, 96-101, 105-108 |
Wildcard | case _: |
Any value, no binding | 34, 59, 81 |
AS Pattern | case x as name: |
Capture with explicit naming | 106-108 |
Sequence | case [x, y]: |
Lists/tuples with destructuring | 116-121, 125-128, 132-146, 150-153 |
Star Pattern | case [first, *rest]: |
Variable-length sequences | 133-137, 142-153 |
Mapping | case {"key": val}: |
Dictionaries with key matching | 162-166, 170-173, 177-181, 184-187 |
Double-Star | case {**rest}: |
Remaining dict items | 178-181 |
Class | case Point(x=a, y=b): |
Object types with field matching | 196-198, 202-211, 215-218, 222-225 |
OR Pattern | case 200 \| 201: |
Multiple alternatives | 234-242 |
Guard Clause | case x if x < 10: |
Pattern with condition | 250-257, 261-272 |
LITERAL PATTERNS#
Literal patterns match exact values - integers, floats, or strings.
Integer Literals (lines 25-35)
The most basic pattern type. Line 25 sets status_code = 200
, which matches the pattern on line 27, executing line 28 to print "OK". Each case tests for a specific integer value.
Float Literals (lines 37-47)
Line 38 sets pi = 3.14
. The match on line 39 compares this against float literals, matching line 40 and printing "Matched pi".
String Literals (lines 49-59)
Line 50 sets command = "start"
. String matching on lines 51-59 tests against different command strings, matching line 52 and executing line 53.
Key Points:
- Literals match using equality comparison
- Type must match exactly (no automatic conversion)
- The wildcard _
(lines 34, 46, 59) catches unmatched cases
SINGLETON PATTERNS#
Singleton patterns match the special values True
, False
, and None
.
Boolean Singletons (lines 66-72)
Line 66 sets flag = True
. The match on line 67 tests against boolean singletons. Since flag
is True
, line 68 matches and executes line 69.
Booleans are singletons - there's only one True
object and one False
object in the runtime, making identity checking possible.
None Singleton (lines 75-81)
Line 75 sets optional_value = None
. The match on line 76 uses the singleton pattern on line 77 to detect the absence of a value, executing line 78.
Usage:
- Use for optional values and nullable types
- Common in error handling and default value checks
- None
checks are more reliable than equality checks
CAPTURE PATTERNS#
Capture patterns bind the matched value to a variable name for use in the case body.
Basic Capture (lines 88-92)
Line 88 sets value = 42
. Line 90 uses case x:
which matches any value and binds it to x
. Line 91 then prints this captured value.
Wildcard Pattern _
(lines 95-101)
Line 95 sets day = "sunday"
. The wildcard on line 99 matches anything but doesn't bind a value - useful when you need a default case but don't need the value itself.
The difference:
- case x:
- matches and binds to variable x
- case _:
- matches but doesn't bind (you can't reference _
)
AS Pattern - Explicit Naming (lines 104-108)
Line 104 sets number = 100
. Line 106 uses case x as captured_num:
which first matches the pattern x
(capturing to x
), then additionally binds the entire match to captured_num
. This is useful for nested patterns where you want both the whole and parts.
SEQUENCE PATTERNS#
Sequence patterns destructure lists and tuples, extracting individual elements or subsequences.
Exact Match (lines 115-121)
Line 115 sets coords = [1, 2, 3]
. Line 117 uses case [1, 2, 3]:
to match the exact sequence. Since it matches, line 118 executes.
Variable Binding (lines 124-128)
Line 124 sets point = [10, 20]
. Line 126 uses case [x, y]:
which matches a two-element list and binds x=10, y=20
. Line 127 uses these captured values.
Star Patterns - Rest Capture (lines 131-137)
Lines 131-137 demonstrate the *
operator for capturing variable-length subsequences:
Line 131: numbers = [1, 2, 3, 4, 5]
Line 133: case [first, *middle, last]:
- Binds first=1
- Binds middle=[2, 3, 4]
(all middle elements)
- Binds last=5
This is similar to Python's unpacking but in pattern context.
Star at Different Positions (lines 140-146)
Line 140: data = [100, 200, 300, 400]
Line 142: case [*start, 400]:
captures everything except the last element
- start = [100, 200, 300]
Line 144: case [100, *rest]:
captures everything after the first element
- rest = [200, 300, 400]
Nested Sequences (lines 149-153)
Line 149: matrix = [[1, 2], [3, 4]]
Line 151: case [[a, b], [c, d]]:
matches a 2x2 nested structure
- Binds a=1, b=2, c=3, d=4
This demonstrates pattern matching on arbitrarily nested data structures.
MAPPING PATTERNS#
Mapping patterns match dictionaries, allowing key-based destructuring and partial matching.
Exact Key Match (lines 160-166)
Line 160: user = {"name": "Alice", "age": 30}
Line 162: case {"name": "Alice", "age": 30}:
matches when both keys exist with exact values.
Unlike sequence patterns, mapping patterns support partial matching - the dict can have extra keys.
Value Capture (lines 169-173)
Line 169: person = {"id": 123, "role": "admin"}
Line 171: case {"id": user_id, "role": user_role}:
- Matches if keys exist
- Binds user_id=123
and user_role="admin"
Double-Star Rest Capture (lines 176-181)
Line 176: config = {"host": "localhost", "port": 8080, "debug": True, "timeout": 30}
Line 178: case {"host": h, "port": p, **rest}:
- Binds h="localhost"
, p=8080
- Captures remaining items in rest = {"debug": True, "timeout": 30}
The **rest
pattern is similar to **kwargs
in function parameters.
Nested Mappings (lines 184-187)
Line 184: response = {"status": 200, "data": {"name": "Bob", "score": 95}}
Line 186: case {"status": 200, "data": {"name": n, "score": s}}:
Demonstrates matching nested dictionary structures and extracting deeply-nested values.
CLASS PATTERNS#
Class patterns match object instances and destructure their fields.
Basic Class Pattern (lines 194-198)
Lines 4-7 define the Point
class.
Line 194: p1 = Point(x=5.0, y=10.0)
Line 196: case Point(x=x_val, y=y_val):
- Checks that p1
is a Point
instance
- Binds x_val=5.0
, y_val=10.0
Value Matching with Classes (lines 201-211)
Line 201: origin = Point(x=0.0, y=0.0)
Multiple cases test for different conditions: - Line 203: Matches origin exactly - Line 205: Matches any point on y-axis (x must be 0.0, y is captured) - Line 207: Matches any point on x-axis (x is captured, y must be 0.0) - Line 209: Wildcard for other points
This demonstrates mixing literal values with capture variables in class patterns.
Nested Class Patterns (lines 214-218)
Lines 8-11 define the Circle
class with a Point
field.
Line 214: circle = Circle(center=Point(x=3.0, y=4.0), radius=5.0)
Line 216: case Circle(center=Point(x=cx, y=cy), radius=r):
This pattern:
1. Checks circle
is a Circle
instance
2. Extracts the center
field, checking it's a Point
3. Extracts x
and y
from that nested Point
4. Extracts radius
from the Circle
Class Pattern with AS (lines 221-225)
Line 221: rect = Rectangle(width=10.0, height=20.0)
Line 223: case Rectangle(width=w, height=h) as captured_rect:
- Binds field values w=10.0
, h=20.0
- Binds entire object to captured_rect
Useful when you need both the object and its fields.
OR PATTERNS#
OR patterns allow multiple alternative patterns using the |
operator.
Multiple Alternatives (lines 232-242)
Line 232: code = 404
Line 234: case 200 | 201 | 204:
matches any success status code
Line 236: case 400 | 401 | 403 | 404:
matches client errors
The |
operator creates an OR pattern - if any alternative matches, the case executes. Since code
is 404, line 236 matches and line 237 executes.
Usage: - Group related values into categories - Reduce duplication in case clauses - Can be used with any pattern type, not just literals
GUARD CLAUSES#
Guards add boolean conditions to patterns using the if
keyword.
Range Checking (lines 249-257)
Line 249: age = 25
Line 251: case x if x < 18:
- pattern x
captures the value, then checks if x < 18
Since age
is 25:
- Line 251 matches pattern but fails guard (25 >= 18)
- Line 253 matches pattern and passes guard (25 < 65)
- Line 254 executes
Grading Example (lines 260-272)
Line 260: score = 85
The match implements a grading scale using guards:
- Line 262: case s if s >= 90:
for grade A
- Line 264: case s if s >= 80:
for grade B (matches since 85 >= 80)
- Subsequent cases handle lower grades
Guards are evaluated after pattern matching succeeds, providing fine-grained filtering.
COMBINED PATTERNS#
Complex real-world scenarios often combine multiple pattern types.
Nested Pattern Combination (lines 279-287)
Line 279: shape_data = {"type": "circle", "center": [0, 0], "radius": 10}
Line 281: case {"type": "circle", "center": [x, y], "radius": r}:
This single pattern combines:
1. Mapping pattern: Matches dictionary structure
2. Literal pattern: Checks "type"
is "circle"
3. Sequence pattern: Destructures "center"
as [x, y]
4. Capture pattern: Binds x
, y
, and r
to variables
Multiple Statements (lines 290-303)
Lines 292-295 show that case bodies can contain multiple statements. After matching "success": 1. Prints success message 2. Sets status variable 3. Prints status code
No break
needed - execution automatically exits after the case body.
WHEN TO USE MATCH STATEMENTS#
Match statements excel when you need to:
- Handle different data types or structures: Distinguish between different object types or data shapes
- Destructure collections: Extract values from nested lists, tuples, or dictionaries
- Apply conditional logic: Use guards for range checks and complex conditions
- Write cleaner code: Replace verbose if-elif-else chains with declarative patterns
- Make intent clear: Show which patterns you're handling explicitly
Pattern Matching vs If-Else
Compare these approaches for handling HTTP status codes:
// Using if-else (imperative)
if code == 200 or code == 201 or code == 204:
print("Success");
elif code == 400 or code == 401 or code == 403 or code == 404:
print("Client error");
elif code == 500 or code == 502 or code == 503:
print("Server error");
else:
print("Other code");
// Using match (declarative)
match code {
case 200 | 201 | 204:
print("Success");
case 400 | 401 | 403 | 404:
print("Client error");
case 500 | 502 | 503:
print("Server error");
case _:
print("Other code");
}
The match version is more declarative, making the structure and intent clearer.
PATTERN MATCHING BEST PRACTICES#
- Order matters: Place more specific patterns before general ones
- Use wildcards: Always include a
case _:
as the last case for safety - Combine patterns: Use OR patterns to group related cases
- Guard sparingly: Prefer specific patterns over guards when possible
- Name captures clearly: Use descriptive variable names in patterns
- Keep cases simple: If a case body is complex, extract to a function
- Document complex patterns: Add comments for nested or intricate patterns
Example of pattern ordering:
match point {
case Point(x=0.0, y=0.0): // Most specific - origin
print("Origin");
case Point(x=0.0, y=y): // Specific - y-axis
print(f"Y-axis at {y}");
case Point(x=x, y=y): // General - any point
print(f"Point at ({x}, {y})");
}
If you reversed the order, the general pattern would always match first, and the specific cases would never execute.