Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Pattern Matching

Pattern matching in milang uses the -> operator to dispatch on a value’s shape. There are no keywords — -> is an expression that evaluates the first alternative whose pattern matches.

Basic Syntax

Write expr -> followed by alternatives. Each alternative is pattern = body. Alternatives can appear inline (separated by ;) or indented on separate lines.

Inline:

classify x = x -> 0 = "zero"; 1 = "one"; _ = "other"
a = classify 0
b = classify 1
c = classify 42
build =  {target = c, os = linux, arch = x86_64}
classify = <closure>
a = zero
b = one
c = other

Indented:

classify x = x ->
  0 = "zero"
  1 = "one"
  _ = "other"
a = classify 0
b = classify 1
c = classify 42
build =  {target = c, os = linux, arch = x86_64}
classify = <closure>
a = zero
b = one
c = other

Literal Patterns

Integers and strings match by exact value:

describe n = n ->
  0 = "zero"
  1 = "one"
  _ = "many"
a = describe 0
b = describe 1
c = describe 99
build =  {target = c, os = linux, arch = x86_64}
describe = <closure>
a = zero
b = one
c = many

Variable Patterns

A lowercase name matches any value and binds it for use in the body:

myAbs x = x ->
  n | n >= 0 = n
  n = 0 - n
a = myAbs 5
b = myAbs (0 - 3)
build =  {target = c, os = linux, arch = x86_64}
myAbs = <closure>
a = 5
b = 3

Wildcard

_ matches any value without binding it. Use it for catch-all branches:

isZero x = x ->
  0 = 1
  _ = 0
a = isZero 0
b = isZero 7
build =  {target = c, os = linux, arch = x86_64}
isZero = <closure>
a = 1
b = 0

Constructor Tag Patterns

Match on a tagged record’s constructor. After matching, the scrutinee’s fields are accessible through dot notation:

Shape = {Circle radius; Rect width height}
area shape = shape ->
  Circle = 3.14 * shape.radius * shape.radius
  Rect = shape.width * shape.height
a = area (Circle 5)
b = area (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
area = <closure>
a = 78.5
b = 12

With named-field destructuring in the pattern, fields are bound directly:

Shape = {Circle radius; Rect width height}
area shape ->
  Circle {radius} = 3.14 * radius * radius
  Rect {width; height} = width * height
a = area (Circle 5)
b = area (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
area = <closure>
a = 78.5
b = 12

List Patterns

Match a list by its elements. [a, b, c] matches a list of exactly three elements. [first, ...rest] matches one or more elements, binding the tail:

xs = [10, 20, 30, 40]
result = xs ->
  [a, b, ...rest] = {first = a; second = b; rest = rest}
  [] = {first = 0; second = 0; rest = []}
build =  {target = c, os = linux, arch = x86_64}
xs = [10, 20, 30, 40]
result =  {first = 10, second = 20, rest = [30, 40]}

An empty-list pattern matches [] (Nil):

isEmpty xs = xs ->
  [] = "empty"
  _ = "non-empty"
a = isEmpty []
b = isEmpty [1]
build =  {target = c, os = linux, arch = x86_64}
isEmpty = <closure>
a = empty
b = non-empty

Guards

A guard adds a condition to an alternative using | condition before the =. The alternative only matches when both the pattern and the guard are satisfied:

classify x = x ->
  n | n < 0 = "negative"
  n | n == 0 = "zero"
  _ = "positive"
a = classify (0 - 5)
b = classify 0
c = classify 10
build =  {target = c, os = linux, arch = x86_64}
classify = <closure>
a = negative
b = zero
c = positive

Guard-Only Matching

When every alternative uses only a guard (no structural pattern), you can write guards directly after ->:

classify x = x ->
  | x < 0 = "negative"
  | x == 0 = "zero"
  | _ = "positive"
a = classify (0 - 5)
b = classify 0
c = classify 10
build =  {target = c, os = linux, arch = x86_64}
classify = <closure>
a = negative
b = zero
c = positive

Combined Pattern + Guard

A constructor or literal pattern can be paired with a guard:

Shape = {Circle radius; Rect width height}
safeArea shape ->
  Circle {radius} | radius > 0 = 3.14 * radius * radius
  _ = 0
a = safeArea (Circle 5)
b = safeArea (Circle 0)
c = safeArea (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
safeArea = <closure>
a = 78.5
b = 0
c = 0

Match in Function Bindings

The f param -> sugar defines a function that immediately matches its last parameter, avoiding an extra = param -> layer:

Shape = {Circle radius; Rect width height}
describe label shape ->
  Circle = label + ": circle"
  Rect = label + ": rect"
  _ = label + ": unknown"
a = describe "shape" (Circle 5)
b = describe "shape" (Rect 3 4)
build =  {target = c, os = linux, arch = x86_64}
Shape = _module_ {Circle = <closure>, Rect = <closure>}
Circle = <closure>
Rect = <closure>
describe = <closure>
a = shape: circle
b = shape: rect

Exhaustiveness

When the compiler can determine the type of a scrutinee (e.g., from a :: type annotation), it checks that all constructors of a union type are covered. If any constructor is missing and there is no wildcard _ catch-all, the compiler emits a warning:

warning: non-exhaustive patterns for Shape — missing: Rect

To silence the warning, either cover all constructors explicitly or add a wildcard branch:

area s = s ->
  Circle = 3.14 * s.radius * s.radius
  _ = 0  -- catch-all for all other shapes

Exhaustiveness checking only triggers when the scrutinee type is a known union type from a :: annotation. Unannotated scrutinees without a catch-all will compile without warning but may fail at runtime if an unmatched constructor is encountered.

Matching Maybe

matchMaybe m = m ->
  Just {val} = "Just(" + toString val + ")"
  Nothing = "Nothing"

main world =
  world.io.println (matchMaybe (Just 5))
  world.io.println (matchMaybe Nothing)
Just(5)
Nothing