Data & Control

Patterns & Control Flow

Choose if, match, or for, then use patterns to select and bind data.

Use if for one yes-or-no condition, match when the shape or category of a value chooses the result, and for when the same work should happen for each item. Patterns are the forms inside match and for that both test data and give names to the parts you need.

Run one example

Save this as data-control.tpz:

TOPAZ
record StudyTask {
    title: string,
    minutes: int,
    done: bool = false,
}

function pace(task: StudyTask) -> string {
    if task.done {
        return "done"
    }
    match task.minutes {
        case 1..15 => "quick"
        case 16..30 => "focused"
        case _ => "deep"
    }
}

let tasks: Array<StudyTask> = [
    StudyTask { title: "Run first program", minutes: 10, done: true },
    StudyTask { title: "Build application", minutes: 25 },
]
let mut remaining = 0
for task in tasks {
    if !task.done {
        remaining = remaining + task.minutes
    }
    print("{task.title}: {pace(task)}")
}
print("remaining: {remaining} minutes")

Check it before running it:

BASH
topaz check data-control.tpz
topaz run data-control.tpz

The result follows the array order:

Output
Run first program: done
Build application: focused
remaining: 25 minutes

Follow the decisions

The loop binds each array element to task. The inner if asks one boolean question: is this task unfinished? It neither needs to classify several cases nor take the value apart.

When both outcomes matter, write if condition { ... } else { ... }. Compatible branch values make the whole if a value.

pace first uses if for the exceptional completed case. Its match then evaluates task.minutes once and tries the case arms from top to bottom. 1..15 and 16..30 are range patterns. _ is the catch-all, so every other integer has a result.

ChooseWhen it fitsWhat it produces
ifOne condition decides between branchesA value when compatible branches produce values; otherwise an effect-only statement
matchA value has several meaningful shapes or categoriesThe selected arm’s value, with any pattern bindings in that arm
forEvery item should be visited in iteration orderEffects as a statement, or an Array of body results in expression position

Patterns select and bind

A literal or range pattern tests a value. A name such as task binds the matched value. Constructor patterns such as Some(value) and Err(message) open Option and Result values. Record patterns select named fields.

Array patterns use .., not call spread syntax. case [head, ..tail] => binds the first element and gives the remaining elements to tail as an Array. Use _ when you need a fallback but do not need the value.

if let pattern = value { ... } is useful for one success shape and an optional fallback. while let repeats the same test until it no longer matches. Their bindings are immutable and exist only inside the successful body.

Common mistake

Do not write [head, ...tail] in a pattern. ... is used for array and call spread; the canonical list-rest pattern is [head, ..tail].

Enum construction and enum matching also look deliberately different. Construct with the enum name, such as Status.Paused("review"), but match the variant without qualification: case Paused(reason) =>.

Exact behavior that affects a choice

  • A value-producing if or match needs compatible branch results.
  • A closed enum match is checked for complete variant coverage. A guarded arm does not count toward that coverage.
  • When the checker cannot prove a match complete, use an explicit _ arm.
  • Patterns test from left to right. Bindings from a failed alternative do not leak into the next one.
  • A refutable pattern that fails in a collection comprehension is an error, not a filter. Add an explicit comprehension if clause to skip items.
  • Array, integer ranges, Set, and map.keys have defined iteration order. Strings are not directly iterable.
  • while and statement-position for are valueless. A for used as an expression collects its body results in source order.

User-defined patterns, generators, general user-defined iteration, fault-catching control forms, and labels on while or for are not part of the current language.

Continue from the course example in Data and Control. See Records & Nominal Data for the shapes patterns can open and Collections & Comprehensions for iteration and filtering.