Pattern Matching

match dispatches on the structure and value of data. The compiler errors on non-exhaustive patterns — every match must end with a wildcard (_) or a variable pattern to cover all cases. Any unbound symbol works as a wildcard.

Basic patterns

Literal values (numbers, keywords, strings, booleans) match by equality:

(defn describe [val]
  (match val
    0      "zero"
    1      "one"
    _      "other"))

(describe 0)               # => "zero"
(describe 1)               # => "one"
(describe 42)              # => "other"

Binding patterns

Unbound symbols in patterns bind the matched value — they do not compare against variables in scope. Use case (see control.md) for equality dispatch against evaluated expressions.

(defn first-or-default [lst fallback]
  (match lst
    (x & _) x
    _       fallback))

(first-or-default (list 10 20) :none)  # => 10
(first-or-default (list) :none)        # => :none

Important: a bare symbol always binds, never compares:

(def x 42)
(match 99
  x x       # x binds to 99, body returns 99
  _ :no)    # => 99, NOT :no

To dispatch against a variable's value, use case or a guard:

(def quit-code 0x100)

# case — evaluates keys, compares with =
(case etype
  quit-code :quit
  :other)

# match — guard compares explicitly
(match etype
  t when (= t quit-code) :quit
  _ :other)

Or-patterns

(or ...) in a pattern matches any of the listed alternatives:

(defn parity [n]
  (match n
    (or 1 3 5 7 9) :odd
    (or 0 2 4 6 8) :even
    _              :out-of-range))

(parity 3)                 # => :odd
(parity 4)                 # => :even
(parity 42)                # => :out-of-range

Array and struct patterns

(defn point-type [p]
  (match p
    [0 0]    :origin
    [x 0]    :x-axis
    [0 y]    :y-axis
    [x y]    :general
    _        :unknown))

(point-type [0 0])         # => :origin
(point-type [5 0])         # => :x-axis
(point-type [3 4])         # => :general

Struct patterns match by key, with literal values for dispatch:

(defn area [shape]
  (match shape
    {:type :circle :radius r}  (* 3.14159 r r)
    {:type :square :side s}    (* s s)
    _                          0))

(area {:type :circle :radius 5})   # => 78.53975
(area {:type :square :side 7})     # => 49

Nested patterns

Patterns compose to any depth:

(defn db-host [config]
  (match config
    {:db {:host h}} h
    _               "unknown"))

(db-host {:db {:host "pg.local"}})   # => "pg.local"
(db-host {:nodb true})               # => "unknown"

Or-patterns

(or p1 p2 ...) matches if any alternative matches. All alternatives must bind the same set of variables (or none at all).

(defn classify-suit [suit]
  (match suit
    (or :hearts :diamonds) :red
    (or :clubs :spades)    :black
    _                      :unknown))

(classify-suit :hearts)    # => :red
(classify-suit :spades)    # => :black

Or-patterns work with binding patterns — each alternative must bind the same names:

(defn first-element [coll]
  (match coll
    (or [x & _] (x & _))  x
    _                      nil))

(first-element [10 20])        # => 10
(first-element (list 30 40))   # => 30

Guards

when inside an arm adds a condition. The syntax is (pattern when condition body)when is a bare keyword between the pattern and the body, not wrapped in parentheses:

(defn classify [n]
  (match n
    x when (> x 0) :positive
    0               :zero
    x               :negative))

(classify 5)               # => :positive
(classify 0)               # => :zero
(classify -3)              # => :negative

Guards can reference bindings from the pattern:

(defn describe-pair [p]
  (match p
    [a b] when (> a b) "descending"
    [a b] when (= a b) "equal"
    [a b]              "ascending"
    _                  "not a pair"))

match vs case vs cond

matchcasecond
Dispatchstructural patternsequality (=) against evaluated expressionsarbitrary test expressions
Variablesbare symbols bindkeys are evaluated and comparedfull expressions
Exhaustivenesscompiler-enforcednono
Use whendispatching on shape, type, or literal valuesdispatching against runtime valuesmulti-branch boolean logic
# match: literal keyword patterns
(match event-type
  :quit      (handle-quit)
  :key-down  (handle-key ev)
  _          nil)

# case: dispatch against variables holding event codes
(case raw-event-code
  event-quit      (handle-quit)
  event-key-down  (handle-key ev)
  (handle-unknown))

# cond: arbitrary boolean conditions
(cond
  (> x 10) :large
  (> x 0)  :small
  (= x 0)  :zero
  :negative)

When cond branches are all testing the same expression against literal values, match is more concise and catches incomplete coverage at compile time. See control.md for cond and case.


See also