Learn Topaz

Data and Control

Model study tasks as nominal records in a collection, then choose if, match, and for to handle three distinct decisions.

Outcome: Model a small domain using a record and a collection, then select if, match, and for based on the specific question each control structure answers.

Prerequisite: Complete Values and Functions and understand bindings, type annotations, parameters, and function return values.

Give each task a shape

Create 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")

Run the standard two-step workflow:

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

It prints:

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

Follow the three decisions

A record is a named data structure that groups related fields into a single nominal type. Defining a record provides explicit names and types for each property instead of relying on loose values or unlabelled positional tuples. This choice keeps domain attributes bundled together under a clear name. In the example code, record StudyTask defines this shape with title, minutes, and done fields, where done defaults to false when omitted during construction.

An Array is an ordered collection that holds multiple elements of the same type in a sequence. An array is chosen here because tasks have a specific order in a study plan, and keeping duplicate or sequential items intact is important for processing them in order. Alternative collection structures focus on key lookup or uniqueness rather than ordered traversal. In the example code, let tasks: Array<StudyTask> = [ ... ] creates an ordered sequence holding two StudyTask record instances.

While record and Array establish the shape and organization of data, control flow structures direct how that data is processed. The constructs if, match, and for handle distinct execution decisions to evaluate data elements.

A for loop is a control structure that iterates over each item in a collection sequentially. A for loop is chosen here because every task in the array needs to be visited once in order to compute total minutes and display results. Other constructs would require managing index positions manually or checking conditions repeatedly without directly tracking the collection layout. In the example code, for task in tasks { ... } binds each element of tasks to the variable task during each step of the iteration.

An if statement is a control structure that evaluates a single boolean expression to decide whether to execute a block of code. An if statement is chosen here because the decision depends on a binary true or false condition rather than multi-way branching or value matching. Using if keeps simple conditional checks clear without unnecessary syntax. In the example code, if !task.done inside the loop checks whether a task is unfinished before adding its minutes to remaining, while if task.done inside pace performs an early return for completed tasks.

A match expression is a control structure that compares a value against pattern cases and produces a result from the matching arm. A match expression is chosen here because classifying numeric ranges into distinct category labels requires choosing among multiple possible outcomes. Using match handles multi-case classification directly instead of chaining multiple boolean conditional branches together. In the example code, match task.minutes evaluates task.minutes against range patterns and wildcards to return a descriptive pace string.

Checking whether a match expression handles every possible value pattern is a key language behavior. When every case is covered, unexpected unhandled values cannot cause fallback errors or logic gaps. Including the wildcard pattern case _ => accounts for any integer outside the specified numeric ranges, confirming that every potential input yields a valid outcome.

Decision: Array, Map, or Set?

Use Array when element order and duplicate values are meaningful, as demonstrated in this study plan. Use Map when key-based lookup is the primary operation. Use Set when element uniqueness takes precedence over position. Choose a collection based on the required observable behavior rather than current dataset size.

Try this

Add StudyTask { title: "Review output", minutes: 5, done: true } as the third element in the array. Which new line appears, and does remaining change?

Answer

The new output line is Review output: done. remaining stays at 25 minutes because the added task is already marked complete, so the if body does not execute for it.

Ready to continue when

You can explain why StudyTask is defined as a record, why the collection uses Array, why for handles iteration, why if manages accumulation, and why match performs classification.

See Patterns & Control Flow, Records & Nominal Data, and Collections & Comprehensions for detailed reference rules. Continue to Failures and Resources.