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

Level 12. The Match Game

Sometimes you have many choices. Instead of nested if statements, Hica uses match. It’s like a sorting machine: drop a value in, and it lands in the right slot!

fun describe(x) => match x {
  0 => "zero",
  1 => "one",
  _ => "many"       // The '_' means "anything else"
}

fun main() {
  let label = describe(1)
  println(label)
}

The _ is like a big bucket that catches everything that didn’t match the other slots. You should always include it so nothing falls through!

Match with conditions (guards)

Sometimes a simple value isn’t enough. You want to check a condition too. Add if after the pattern to create a guard:

fun classify(n) => match n {
  x if x < 0    => "negative",
  0             => "zero",
  x if x > 100  => "big",
  _             => "small positive"
}

fun main() {
  println(classify(-5))    // "negative"
  println(classify(0))     // "zero"
  println(classify(200))   // "big"
  println(classify(42))    // "small positive"
}

The variable x captures the value, and if x < 0 is the guard, like a bouncer who checks your ticket before letting you through the door.

Or-patterns: matching several values at once

Use | to match multiple values in one arm, like saying “this or that”:

fun day_type(day) => match day {
  "Saturday" | "Sunday" => "weekend",
  _                     => "weekday"
}

fun classify(n) => match n {
  1 | 2 | 3 => "low",
  4 | 5 | 6 => "mid",
  _         => "high"
}

fun main() {
  println(day_type("Sunday"))   // "weekend"
  println(classify(2))          // "low"
}

Range patterns: matching a whole range

Instead of listing every number, use ..= to match a range (both ends included):

fun grade(score: int) => match score {
  0..=59   => "F",
  60..=69  => "D",
  70..=79  => "C",
  80..=89  => "B",
  90..=100 => "A",
  _        => "invalid"
}

fun main() {
  println(grade(85))    // "B"
  println(grade(92))    // "A"
  println(grade(45))    // "F"
}

Think of 0..=59 as “any number from 0 to 59, including both.” Much shorter than writing 0 | 1 | 2 | ... | 59!

Matching tuples

You can take apart a tuple right inside a match. This is called tuple destructuring:

fun describe(point) => match point {
  (0, 0) => "origin",
  (x, 0) => "on x-axis at {x}",
  (0, y) => "on y-axis at {y}",
  (x, y) => "({x}, {y})"
}

fun main() {
  println(describe((0, 0)))    // "origin"
  println(describe((3, 0)))    // "on x-axis at 3"
  println(describe((2, 5)))    // "(2, 5)"
}

Each arm peeks inside the tuple and names the pieces. It’s like opening a lunchbox and checking what’s in each compartment!

list slice patterns:

You can also peek inside lists! Use brackets to check what’s in the list:

fun describe(xs: list<int>) : string => match xs {
  []           => "empty bag",
  [x]          => "just {x}",
  [x, ..rest]  => "first is {x}, {length(rest)} more inside"
}

fun main() {
  println(describe([]))           // empty bag
  println(describe([42]))         // just 42
  println(describe([1, 2, 3]))    // first is 1, 2 more inside
}

Think of it like looking into a bag: [] means the bag is empty, [x] means there’s exactly one thing, and [x, ..rest] means “grab the first thing and keep the rest in the bag.”

This is perfect for processing a list one item at a time:

fun sum(xs: list<int>) : int => match xs {
  []          => 0,
  [x, ..rest] => x + sum(rest)
}

🎯 Try it: Write a match that labels 0 as "none", 1 as "solo", 2 as "pair", and everything else as "group".

🎯 Try it: Write a function season(month: int) using range patterns: months 3..=5 are "spring", 6..=8 are "summer", 9..=11 are "autumn", and everything else is "winter".