Implementation: Macro in src/hql/lib/macro/core.hql (match, __match_impl__, __match_or_cond__)
Runtime helper: __hql_match_obj in src/common/runtime-helper-impl.ts (object pattern key-existence check)
HQL provides pattern matching via three constructs:
match - Pattern matching expression with multiple casescase - Individual pattern clause with optional guarddefault - Fallback clause when no pattern matchesPattern matching is implemented as a compile-time macro that expands to nested if expressions and IIFEs with JS destructuring.
(match value
(case pattern result)
(case pattern result)
(default fallback))
| Pattern Type | Syntax | Description |
|---|---|---|
| Literal | 42, "hello", true, null | Exact value match (===) |
| Wildcard | _ | Matches anything, no binding |
| Symbol | x | Matches anything, binds to name |
| Array | [a, b], [] | Destructuring array match (checks Array.isArray and .length) |
| Array Rest | [h, & t] | Head and tail destructuring (checks Array.isArray and .length >=) |
| Object | {name: n, age: a} | Destructuring object match (checks type + key existence via __hql_match_obj) |
| Or-pattern | `( | 1 2 3)` |
(match value
(case pattern (if guard-condition) result)
(default fallback))
Guards are checked after pattern binding, allowing use of bound variables.
(match status-code
(case 200 "OK")
(case 404 "Not Found")
(case 500 "Server Error")
(default "Unknown"))
(match x
(case 0 "zero")
(case n (+ "value: " n))) // n binds to x
(match value
(case 1 "one")
(case 2 "two")
(case _ "other")) // _ matches anything
// Empty array
(match arr
(case [] "empty")
(default "not empty"))
// Fixed-length array
(match point
(case [x, y] (+ x y))
(default 0))
// Rest pattern (head & tail)
(match numbers
(case [] 0)
(case [h, & t] (+ h (sum t))))
(match user
(case {name: n, age: a} (+ n " is " a " years old"))
(default "unknown user"))
// Single key
(match config
(case {port: p} p)
(default 8080))
Object patterns check that the value is a non-null, non-array object and that all specified keys exist in the object (via __hql_match_obj). Binding uses JS destructuring, so missing keys yield undefined.
(match n
(case x (if (> x 0)) "positive")
(case x (if (< x 0)) "negative")
(default "zero"))
// Guard with array binding
(match pair
(case [a, b] (if (> a b)) "a > b")
(case [a, b] (if (< a b)) "a < b")
(default "a = b"))
// Match any of several values
(match status-code
(case (| 200 201 204) "success")
(case (| 400 422) "client error")
(case (| 500 502 503) "server error")
(default "unknown"))
// Works with strings
(match day
(case (| "Saturday" "Sunday") "weekend")
(case _ "weekday"))
// Works with null
(match value
(case (| null undefined) "missing")
(case x (+ "got: " x)))
Or-patterns do not bind variables. They expand to chained === checks via the __match_or_cond__ helper macro.
// Nested arrays
(match matrix
(case [[a, b], [c, d]] (+ a b c d))
(default 0))
// Object with array value
(match point
(case {coords: [x, y]} (+ x y))
(default 0))
// Sum of list
(fn sum [lst]
(match lst
(case [] 0)
(case [h, & t] (+ h (sum t)))))
(sum [1, 2, 3, 4, 5]) // => 15
// Length of list
(fn my-length [lst]
(match lst
(case [] 0)
(case [_, & t] (+ 1 (my-length t)))))
(my-length [1, 2, 3, 4]) // => 4
Three macros in src/hql/lib/macro/core.hql:
match - Entry point. Binds value to a gensym variable (val#), dispatches to __match_impl__.__match_impl__ - Recursive clause processor. Classifies each pattern, generates condition + body + fallback chain.__match_or_cond__ - Helper for or-patterns. Builds (|| (=== val p1) (=== val p2) ...) recursively.The __match_impl__ macro classifies each pattern at macro-expansion time:
| Classification | Detection | Condition Generated |
|---|---|---|
default | clause starts with default | (none, unconditional) |
| Wildcard | symbol named _ | true (always matches) |
| Symbol binding | any other symbol (not _, null) | true (always matches) |
| Null literal | symbol named null | (=== val null) |
| Or-pattern | list starting with | | (__match_or_cond__ val ...alternatives) |
| Object | list starting with hash-map or __hql_hash_map | (__hql_match_obj val (quote pattern)) |
| Array (no rest) | other list, no & | (and (Array.isArray val) (=== (js-get val "length") n)) |
| Array (with rest) | list with & at second-to-last | (and (Array.isArray val) (>= (js-get val "length") k)) |
| Other literal | anything else | (=== val literal) |
| Pattern Type | Body |
|---|---|
| Symbol binding | (let (sym val) result) |
| Array/Object | ((fn [pattern] result) val) (IIFE with destructuring parameter) |
| Or-pattern, wildcard, null, literal | result (no binding) |
| With guard | wraps body in (if guard-expr result fallback) |
When the condition is true (wildcard, symbol binding), the if wrapper is omitted and the body is emitted directly.
let or IIFE destructuring parameterArray.isArray, typeof, and key-existence checksIf no clause matches and no default is provided, throws an error that includes the unmatched value:
(match 999
(case 1 "one")
(case 2 "two"))
// throws: Error("No matching pattern for value: 999")
If a clause is not case or default:
(match x
(when true "yes")) // not a valid clause type
// throws: Error("Invalid match clause")
Tests are in tests/unit/pattern-matching.test.ts.
[][x][a, b][h, & t] (extracts head and tail){name: n, age: a}{x: val}[[a, b], [c, d]]{coords: [x, y]}[h, & t][_, & t]match keywordcase keyword{status: 200} is not supported; use guards instead)typeof)cond - Multi-way conditional (simpler than match)if-let - Conditional bindingwhen-let - Conditional execution with binding