Match Statements
match lets you compare a value against several patterns and run the first arm that fits.
Calibre also has fn match, which is a convenient way to define a function whose body is entirely pattern matching.
let classify := fn match int -> str { 0 => "zero", 1 => "one", _ => "many"};An ordinary function and a fn match function can often express the same idea however
the fn match form is usually shorter and keeps the focus on the patterns themselves.
const classify1 := fn (x : int) -> str => match x { 0 => "zero", _ => "many"};
const classify2 := fn match int -> str { 0 => "zero", _ => "many"};Each arm can match a different pattern shape, and _ is the fallback wildcard.
You can match simple literal values directly.
match 50 { 10 => print("ten"), 50 => print("fifty"), _ => print("other")};Multiple alternatives can be combined with |.
match 30 { 10 | 20 | 30 => print("small"), _ => print("other")};Ranges can be matched with in.
let classify := fn match int -> str { in 0..20 => "in-range", _ => "other"};You can also match membership in a literal list.
let classify := fn match int -> str { in [30, 40, 50] => "in-list", _ => "other"};@ bindings let you capture part of what matched and is arms match based on type.
Here, is int says the arm expects an int, and v @ ... binds the matched value so it can also be used in a guard like if v > 100.
let classify := fn match int -> str { v @ is int if v > 100 => "large-int", _ => "other"};The if part of an arm is a guard. A guard adds an extra boolean condition after the pattern itself has already matched.
let classify := fn match int -> str { in 0..20 if true => "small-range", let v if v > 100 => "large-int", _ => "other"};let arms bind whatever value matched to a name.
This not usually required and can usually be inferred but if a variable with the same name you want to use exists in the scope, it can cause it to be treated as a comparison instead.
Therefore let, mut or const can be used to force shadowing and these keywords can also be used within destructuring for the same purpose.
match 50 { let value => print(value), _ => {}};Tuples can be matched position by position.
match 10, 90, 20 { 10, mut value, 20 => { value += 90; print(value); }, 10, const value, _ => print(value), _ => {}};The .. pattern can ignore large parts of a tuple or list unlike _ which only ignores a single value.
match 10, 90, 20 { 10, .., 20 => print("matched"), _ => {}};Structs can be destructured directly in a match arm.
type Pair := struct { left right : int };
match Pair { left : 30, right : 50 } { {left : 50, right} => print(50 - right), {left, right} => print(left + right), _ => {}};Enums can be matched by variant, and their payloads can be destructured at the same time.
type PairEnum := enum { Normal : Pair, Tuple : <int, int> };
match PairEnum.Tuple : (10, 30) { .Normal : {left: l, right: r} => print(l + r), .Tuple : (val, 20) => print(val), _ => {}};Lists can be matched by exact shape or by prefix.
let classify_list := fn match list:<int> -> str { [] => "Empty list", [1] => "List of just 1", [4, ..] => "List starting with 4", [_, _] => "List of 2 elements", _ => "Some other list"};Strings can also be matched against to test against how a string input starts and ends easily.
let parse_command := fn match str -> str { "go:" & direction => "move " & direction, head @ "noop" & _ => "saw " & head, _ => "unknown"};match can also be used without an explicit value, where each arm acts like a checked condition.
let age := "10";
match { age := "10" => print("ten"), _ => print("other")};