plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-examples/match.plum
import std/Option
import std/Bool
import std/Number
import std/Str
enum Color =
| Red
| Green
| Blue
fun describeNumber(n: Int) -> Str =
match n
0 => "zero"
1 => "one"
_ => "many"
fun describeBool(b: Bool) -> Int =
match b
True => 1
False => 0
fun bindExample(n: Int) -> Int =
match n
x => x
fun describeColor(c: Color) -> Str =
match c
Red => "red"
Green => "green"
Blue => "blue"
# A dedicated, non-generic "maybe an Int" — as opposed to the real, generic
# `Option[T]` (`import std/option` above, used elsewhere in this file):
# constructing a bare payload-free variant (`Absent` here, `None` for a real
# generic enum) outside of a `match` pattern can't be disambiguated between
# multiple concrete instantiations of ITS enum from that expression alone
# (see the README's Generics section) — and since this file's forced-in
# stdlib prelude (`plum-core::loader::loadAndMerge`) uses `Option` at several
# OTHER concrete types internally, a bare `None` here genuinely IS
# ambiguous. `IntOpt` sidesteps that entirely by only ever having ONE
# possible instantiation to begin with.
enum IntOpt =
| Present(Int)
| Absent
fun describeOption(opt: IntOpt) -> Int =
match opt
Present(v) => v
Absent => 0
fun main() -> Int =
describeOption(Present(5))
# ---- pattern matching regression tests ----
enum Nested =
| Wrap(IntOpt)
| Empty
fun unwrapNested(n: Nested) -> Int =
match n
Wrap(Present(v)) => v
Wrap(Absent) => -1
Empty => 0
enum GenericOption[T] =
| GSome(T)
| GNone
enum GenericBox[T] =
| GFull(T)
| GEmpty
fun unwrapGenericBox(b: GenericBox) -> Int =
match b
GFull(GSome(v)) => v
GFull(GNone) => -1
GEmpty => 0
enum RecOption =
| RSome(RecOption)
| RNone
fun unwrapTwice(o: RecOption) -> Int =
match o
RSome(RSome(RNone)) => 1
RSome(RNone) => 2
RNone => 3
_ => 0
fun classifyForWildcardTest(a: Int) -> Int =
match a
1 => 100
2 => 200
_ => 0
fun colorCode(c: Color) -> Int =
match c
Red => 1
Green => 2
Blue => 3
fun isSome(o: Option) -> Int =
match o
Some(_) => 1
None => 0
# Mirrors libs/std/bool.plum's `and`/`or`: `match a, b` against two Bool
# subjects, each case naming a tag pattern per position.
fun and(a: Bool, b: Bool) -> Bool =
match a, b
True, True => True
True, False => False
False, True => False
False, False => False
fun andOrCheck() -> Int =
x := and(True, True)
y := and(True, False)
match x, y
True, False => 1
_, _ => 0
fun classifyTwoInts(a: Int, b: Int) -> Int =
match a, b
1, 1 => 100
1, 2 => 200
_, _ => 0
fun combine(a: Int, b: Int) -> Int =
match a, b
0, y => y
x, 0 => x
x, y => x + y
fun bothOptions(a: Option, b: Option) -> Int =
match a, b
Some(x), Some(y) => x + y
_, _ => 0
enum Point =
| Point(x: Int, y: Int)
fun sum(self) -> Int =
match self
Point(x, y) => x + y
fun classify(self) -> Str =
match self
Point(0, 0) => "origin"
Point(x, 0) => "on x axis"
Point(_, _) => "elsewhere"
enum Shape =
| Circle(Point)
| Square(Point)
fun measure(self) -> Int =
match self
Circle(Point(x, y)) => x + y
Square(Point(x, y)) => x * y
# Named payload fields (`Ring(radius: Int)`) alongside the
# unnamed-positional-payload form (`Circle(Point)` above) — lets a
# multi-field variant like `Rect` name each field instead of leaving them as
# anonymous positional types. Pattern matching is unaffected either way
# (still positional, by declaration order — `Rect(w, h)`, not `Rect(w:, h:)`).
enum Figure =
| Ring(radius: Int)
| Rect(w: Int, h: Int)
fun area(self) -> Int =
match self
Ring(radius) => radius * radius
Rect(w, h) => w * h
test "match example computes correctly"
assert main() == 5
test "nested constructor pattern matches and binds runs correctly"
assert unwrapNested(Wrap(Present(5))) == 5
test "nested constructor pattern mismatch falls through to next case runs correctly"
assert unwrapNested(Wrap(Absent)) == -1
test "nested constructor pattern against a specialized generic enum runs correctly"
assert unwrapGenericBox(GFull(GSome(7))) == 7
test "doubly nested constructor pattern runs correctly"
assert unwrapTwice(RSome(RSome(RNone))) == 1
test "match int and wildcard run correctly"
assert classifyForWildcardTest(2) == 200
test "match bool variant pattern runs correctly"
assert describeBool(False) == 0
test "non bool bare tag pattern runs correctly"
assert colorCode(Green) == 2
test "constructor pattern wildcard field runs correctly"
assert isSome(Some(99)) == 1
test "constructor pattern does not misfire on payload free sibling"
assert describeOption(Absent) == 0
test "multi subject match with enum tags runs correctly"
assert andOrCheck() == 1
test "multi subject match falls through to next case when only first position matches"
# The first case's position-0 pattern (`1`) matches, but position-1 (`1`)
# doesn't (b is 2) — codegen must fall through to the *next case* (trying
# its own position 0 again), not just "move on" within the first case.
assert classifyTwoInts(1, 2) == 200
test "multi subject match with binding and wildcard runs correctly"
assert combine(3, 4) == 7
test "multi subject match with generic enum variant runs correctly"
assert bothOptions(Some(3), Some(4)) == 7
test "match destructures a plain class the same way it destructures an enum variant"
assert Point(x: 3, y: 4).sum() == 7
test "match on a plain class supports literal/wildcard sub-patterns and case fallthrough"
assert Point(x: 0, y: 0).classify() == "origin"
assert Point(x: 5, y: 0).classify() == "on x axis"
assert Point(x: 5, y: 5).classify() == "elsewhere"
test "a plain class nested inside an enum variant pattern destructures correctly"
assert Circle(Point(x: 3, y: 4)).measure() == 7
assert Square(Point(x: 3, y: 4)).measure() == 12
test "named-payload enum variant constructs via named args and destructures by position"
assert Ring(radius: 5).area() == 25
assert Rect(w: 3, h: 4).area() == 12
test "named-payload enum variant still supports positional construction too"
assert Ring(5).area() == 25
assert Rect(3, 4).area() == 12