pattern matching, refutable

Patterns available in Erg

variable pattern

# basic assignments
i = 1
# with type
i: Int = 1
# with anonymous type
i: {1, 2, 3} = 2

# functions
fn x = x + 1
# equals
fn x: Add(Int) = x + 1
# (anonymous) function
fn = x -> x + 1
fn: Int -> Int = x -> x + 1

# higher-order type
a: [Int; 4] = [0, 1, 2, 3]
# or
a: Array Int, 4 = [0, 1, 2, 3]

Literal patterns

# Raise a TypeError if `i` cannot be determined to be 1 at compile time.
# omit `_: {1} = i`
1 = i

# simple pattern matching
match x:
    1 -> "1"
    2 -> "2"
    _ -> "other"

# fibonacci function
fib0 = 0
fib1 = 1
fibn: Nat = fibn-1 + fibn-2

constant pattern

cond=False
match! cond:
    True => print! "cond is True"
    _ => print! "cond is False"

PI = 3.141592653589793
E = 2.718281828459045
num = PI
name = match num:
    PI -> "pi"
    E -> "e"
    _ -> "unnamed"

Refinement pattern

# these two are the same
Array(T, N: {N | N >= 3})
Array(T, N | N >= 3)

f M, N | M >= 0, N >= 1 = ...
f(1, 0) # TypeError: N (2nd parameter) must be 1 or more

discard (wildcard) pattern

_ = 1
_: Int = 1
zero_ = 0
right(_, r) = r

If not constrained by context, _ is of type Obj.

Variable length patterns

It is used in combination with the tuple/array/record pattern described later.

[i,...j] = [1, 2, 3, 4]
assert j == [2, 3, 4]
first|T|(fst: T, ...rest: T) = fst
assert first(1, 2, 3) == 1

Tuple pattern

(i, j) = (1, 2)
((k, l), _) = ((1, 2), (3, 4))
# If not nested, () can be omitted (1, 2 are treated as (1, 2))
m, n = 1, 2

f(x, y) = ...

array pattern

[i, j] = [1, 2]
[[k, l], _] = [[1, 2], [3, 4]]

length[] = 0
length[_, ...rest] = 1 + lengthrest

record pattern

record = {i = 1; j = 2; k = 3}
{j; ...} = record # i, k will be freed

{sin; cos; tan; ...} = import "math"
{*} = import "math" # import all

person = {name = "John Smith"; age = 20}
age = match person:
    {name = "Alice"; _} -> 7
    {_; age} -> age

f {x: Int; y: Int} = ...

Data class pattern

Point = Inherit {x = Int; y = Int}
p = Point::{x = 1; y = 2}
Point::{x; y} = p

Nil T = Class Impl := Phantom T
Cons T = Inherit {head = T; rest = List T}
List T = Enum Nil(T), Cons(T)
List T.
    first self =
        match self:
            Cons::{head; ...} -> x
            _ -> ...
    second self =
        match self:
            Cons::{rest=Cons::{head; ...}; ...} -> head
            _ -> ...

enumeration pattern

*Actually, it's just an enumeration type

match x:
    i: {1, 2} -> "one or two: \{i}"
    _ -> "other"

range pattern

*Actually, it is just an interval type.

# 0 < i < 1
i: 0<..<1 = 0.5
# 1 < j <= 2
_: {[I, J] | I, J: 1<..2} = [1, 2]
# 1 <= i <= 5
match i
    i: 1..5 -> ...

Things that aren't patterns, things that can't be patterned

A pattern is something that can be uniquely specified. In this respect pattern matching differs from ordinary conditional branching.

Condition specifications are not unique. For example, to check if the number n is even, the orthodox is n % 2 == 0, but you can also write (n / 2).round() == n / 2. A non-unique form is not trivial whether it works correctly or is equivalent to another condition.

set

There is no set pattern. Because the set has no way to uniquely retrieve the elements. You can retrieve them by iterator, but the order is not guaranteed.