Skip to main content
Pattern matching is a powerful feature that allows you to inspect and destructure values. Elara’s pattern matching supports exhaustiveness checking to ensure all cases are covered.

Basic pattern matching

Use match ... with to match values against patterns:
def describe : Int -> String
let describe n =
    match n with
        0 -> "zero"
        1 -> "one"
        2 -> "two"
        _ -> "many"
The underscore _ is a wildcard pattern that matches any value and discards it.

Pattern types

Variable patterns

Bind matched values to names:
def identity : a -> a
let identity x = match x with
    y -> y  -- y binds to the value of x

Literal patterns

Match specific literal values:
def isZero : Int -> Bool
let isZero n =
    match n with
        0 -> True
        _ -> False

Constructor patterns

Match algebraic data type constructors:
type Option a = Some a | None

def getOrDefault : Option a -> a -> a
let getOrDefault opt default =
    match opt with
        Some x -> x
        None -> default
Real-world example:
type Shape =
    Circle Int
    | Rectangle Int Int
    | Square Int

def area : Shape -> Int
let area shape =
    match shape with
        Circle r -> 3 * r * r
        Rectangle w h -> w * h
        Square s -> s * s

List patterns

Match list structure:
def sum : List Int -> Int
let sum ls =
    match ls with
        [] -> 0
        x :: xs -> x + sum xs

def first : List a -> Option a
let first list =
    match list with
        [] -> None
        x :: _ -> Some x
The :: operator (cons) matches a non-empty list, binding the head to the left and the tail to the right.
From the standard library:
def append : List a -> List a -> List a
let append l1 l2 =
    match l1 with
        Nil -> l2
        Cons x xs -> Cons x (append xs l2)

Tuple patterns

Destructure tuples:
def swap : (a, b) -> (b, a)
let swap pair =
    match pair with
        (a, b) -> (b, a)

def addPair : (Int, Int) -> Int
let addPair pair =
    match pair with
        (x, y) -> x + y
Real example:
def first : (a, b) -> a
let first p =
    match p with
        Tuple2 a b -> a

def zip : List a -> List b -> List (Tuple2 a b)
let zip l1 l2 =
    match (l1, l2) with
        (Nil, _) -> Nil
        (_, Nil) -> Nil
        (Cons x xs, Cons y ys) -> Cons (x, y) (zip xs ys)

Record patterns

Match and destructure record fields:
def getName : { name: String, age: Int } -> String
let getName person =
    match person with
        { name: n, age: _ } -> n

Wildcard patterns

Ignore values you don’t need:
def isNone : Option a -> Bool
let isNone opt =
    match opt with
        Some _ -> False
        None -> True

def second : List a -> Option a
let second list =
    match list with
        _ :: x :: _ -> Some x
        _ -> None

Nested patterns

Patterns can be nested arbitrarily deep:
def describe : Option (List Int) -> String
let describe opt =
    match opt with
        None -> "nothing"
        Some [] -> "empty list"
        Some (x :: xs) -> "list starting with " ++ toString x
From the examples:
def filter : (a -> Bool) -> List a -> List a
let filter p list =
    match list with
        Nil -> Nil
        Cons x xs ->
            if p x then
                Cons x (filter p xs)
            else
                filter p xs

Pattern guards

Add conditional checks to patterns using if:
def classify : Int -> String
let classify n = match n with
    n if n < 0 -> "negative"
    n if n == 0 -> "zero"
    n if n > 0 -> "positive"
Guards can break exhaustiveness checking. The compiler may not be able to verify that all cases are covered when using guards.

As-patterns

Bind the whole matched value while also destructuring:
def example : List Int -> String
let example list = match list with
    ([1, _, _] as l) -> "List starting with 1: " ++ toString l
    _ -> "something else"

Exhaustiveness checking

Elara ensures all patterns are covered:
-- This will compile
def isSome : Option a -> Bool
let isSome opt =
    match opt with
        Some _ -> True
        None -> False

-- This will NOT compile (missing None case)
def unsafeGet : Option a -> a
let unsafeGet opt =
    match opt with
        Some x -> x
        -- Error: non-exhaustive pattern match!
The compiler analyzes all pattern matches to ensure you handle every possible case, preventing runtime errors.

Practical examples

Option type handling

def safeDiv : Int -> Int -> Option Int
let safeDiv x y =
    if y == 0 then
        None
    else
        Some (x / y)

def describe : Option Int -> String
let describe opt =
    match opt with
        Some val -> "Got value: " ++ toString val
        None -> "Got nothing"

let main =
    let res1 = safeDiv 10 2
    let res2 = safeDiv 10 0
    println ("10 / 2: " ++ describe res1) >>
    println ("10 / 0: " ++ describe res2)

Recursive list processing

def map : (a -> b) -> List a -> List b
let map f list =
    match list with
        Nil -> Nil
        Cons x xs -> Cons (f x) (map f xs)

def reverse : List a -> List a
let reverse l =
    let reverseGo acc l_ =
        match l_ with
            Nil -> acc
            Cons x xs -> reverseGo (Cons x acc) xs
    reverseGo Nil l

Complex data structures

type JSONElement
    = JSONString String
    | JSONNumber Int
    | JSONNull
    | JSONArray [JSONElement]
    | JSONObject [{ key: String, value: JSONElement }]

def jsonToString : JSONElement -> String
let jsonToString elem = match elem with
    JSONNull -> "null"
    JSONString str -> str
    JSONNumber num -> toString num
    JSONArray arr -> "[" ++ (String.join ", " (map jsonToString arr)) ++ "]"
    JSONObject components -> 
        let componentToString component = match component with
            { key, value } -> "\"" ++ key ++ "\" : " ++ jsonToString value
        "{" ++ String.join ", " (map componentToString components) ++ "}"

Build docs developers (and LLMs) love