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