FREAKV3 docs freak 0.15.1 (Maverick)

Control flow#

Choose between paths with if and when, and repeat work with loops that count, loops that wait for a condition, and loops that visit a list.

A program that only ran its statements from top to bottom could do very little. This chapter covers the statements that decide what runs next:

  • if, else if and else
  • when, for choosing by value
  • repeat ... times, for a known number of passes
  • repeat until, for looping on a condition
  • training arc, a condition loop that is guaranteed to end
  • for each, for visiting the elements of a list
  • counting loops: ranges, repeat ... times with, and the C-style for
  • while, for looping as long as a condition holds
  • break and continue

if#

if runs a block when a condition is true.

examples/book_if.fk compilesruns
-- if / else if / else
task status(fuel: int) -> word {
    if fuel > 75 {
        give back "full"
    } else if fuel > 25 {
        give back "cruising"
    } else if fuel > 0 {
        give back "bingo fuel"
    } else {
        give back "dry"
    }
}

task main() {
    say status(90)
    say status(40)
    say status(10)
    say status(0)

    pilot fuel = 40
    pilot hostile = true
    if fuel > 25 and not hostile {
        say "continue patrol"
    } else {
        say "return to base"
    }
}
Program output
full
cruising
bingo fuel
dry
return to base

The condition comes straight after if, without parentheses. The block is always in braces, even when it holds a single statement:

diagnostics/v3/book_if_needs_braces.fk does not compile
task main() {
    pilot fuel = 40
    if fuel > 25 say "cruising"
}
Compiler diagnostic
error: expected '{', found 'say'
 --> book_if_needs_braces.fk:3:18
  |
3 |     if fuel > 25 say "cruising"
  |                  ^

error: unexpected end of file — missing '}'
 --> book_if_needs_braces.fk:1:13
  |
1 | task main() {
  |             ^ block opened here

An if can be followed by else, whose block runs when the condition is false. In between you can put any number of else if branches. The conditions are tested from the top, the first true one has its block run, and the rest are skipped. In status, a fuel level of 40 fails the first test, passes the second, and the remaining two branches are never looked at.

That order matters. Had the tests been written the other way round, with `fuel

0` first, every positive level would have been caught by it and reported as

bingo fuel.

The condition must be a bool#

Some languages treat zero as false and anything else as true. FREAK does not. A condition has to be a bool, and nothing else is accepted in its place:

diagnostics/v3/book_if_needs_bool.fk does not compile
task main() {
    pilot fuel = 40
    if fuel {
        say "fuel remaining"
    }
}
Compiler diagnostic
type error: if condition must have type bool, got int (line 3)
 --> book_if_needs_bool.fk:3:1
3 |     if fuel {
  | ^

Say what you mean: if fuel > 0.

Comparisons cannot be chained the way they are in mathematics, either:

diagnostics/v3/book_chained_comparison.fk does not compile
task main() {
    pilot fuel = 40
    if 25 < fuel < 75 {
        say "cruising"
    }
}
Compiler diagnostic
type error: operator '<' does not accept bool and int (line 3)
 --> book_chained_comparison.fk:3:1
3 |     if 25 < fuel < 75 {
  | ^

The message makes sense once you see how the expression is read. 25 < fuel is worked out first and gives a bool. The compiler is then asked to compare that bool with 75. Write the two comparisons separately and join them: if 25 < fuel and fuel < 75.

if is a statement#

An if does something. It does not produce a value, so it cannot stand on the right of an =:

diagnostics/v3/book_if_is_not_a_value.fk does not compile
task main() {
    pilot fuel = 40
    pilot state = if fuel > 25 { "cruising" } else { "bingo" }
    say state
}
Compiler diagnostic
error: unexpected 'if' — this token cannot start an expression
 --> book_if_is_not_a_value.fk:3:19
  |
3 |     pilot state = if fuel > 25 { "cruising" } else { "bingo" }
  |                   ^

error: unexpected 'else' — this token cannot start an expression
 --> book_if_is_not_a_value.fk:3:47
  |
3 |     pilot state = if fuel > 25 { "cruising" } else { "bingo" }
  |                                               ^

There are two ways to get the same effect. Declare the pilot first and assign to it in each branch:

Illustrative snippet; not independently verified. Use the badged executable examples for tested programs.

FREAK
    pilot mut state = ""
    if fuel > 25 {
        state = "cruising"
    } else {
        state = "bingo"
    }

Or put the decision in a task and give the answer back, as status does in the first listing. The second way is usually cleaner, because the pilot that receives the answer is given its value once, and never needs a placeholder.

when#

A chain of else if that compares one value against several possibilities is better written as a when.

examples/book_when.fk compilesruns
-- `when` picks one arm by comparing a value with literals.
task order_for(code: int) -> word {
    when code {
        1 -> give back "launch"
        2 -> give back "hold"
        3 -> give back "abort"
        _ -> give back "unknown order"
    }
}

task main() {
    say order_for(1)
    say order_for(9)

    pilot rank = "ace"
    when rank {
        "rookie" -> say "simulator only"
        "ace" -> {
            say "cleared for sortie"
            say "lead element"
        }
        _ -> say "report to the briefing room"
    }

    -- No arm matches and there is no `_`: nothing runs.
    when rank {
        "cadet" -> say "never printed"
    }
}
Program output
launch
unknown order
cleared for sortie
lead element

when takes a value, called the subject, and a list of arms. Each arm is a literal value, an arrow, and one statement. The subject is compared with each arm's value in turn, and the first arm that is equal has its statement run. The arm written _ matches anything. It is the catch-all.

The listing shows the rest of the rules.

An arm holds one statement. To run several, put them in a block, as the "ace" arm does.

If no arm matches and there is no _, nothing happens, and the program carries on after the when. The compiler does not insist that you cover every case, so a missing arm fails silently. Unless you are certain the listed values are the only ones possible, end with _.

Arms are tried in order, so an arm after _ could never run, and neither could a second arm with a value already listed. The compiler refuses both:

diagnostics/v3/book_when_unreachable.fk does not compile
task main() {
    pilot rank = "ace"
    when rank {
        _ -> say "the catch-all comes first"
        "ace" -> say "never reached"
    }
}
Compiler diagnostic
type error: when arm is unreachable after '_' (line 5)
 --> book_when_unreachable.fk:5:1
5 |         "ace" -> say "never reached"
  | ^

Keep the catch-all at the bottom.

The subject can be an int, a word, a bool or a num.

Arms are literals#

An arm's value has to be written out as a literal. It cannot be a pilot or a calculation:

diagnostics/v3/book_when_needs_literal.fk does not compile
task main() {
    pilot code = 3
    pilot abort_code = 3
    when code {
        abort_code -> say "abort"
        _ -> say "continue"
    }
}
Compiler diagnostic
type error: when case must be a literal or '_' (line 5)
 --> book_when_needs_literal.fk:5:1
5 |         abort_code -> say "abort"
  | ^

When the values you are comparing against are not known until the program runs, use if and else if.

The literal also has to be of the subject's type:

diagnostics/v3/book_when_wrong_type.fk does not compile
task main() {
    pilot code = 3
    when code {
        1 -> say "launch"
        "abort" -> say "abort"
    }
}
Compiler diagnostic
type error: when case type word does not match target type int (line 5)
 --> book_when_wrong_type.fk:5:1
5 |         "abort" -> say "abort"
  | ^

when in a task that gives back#

Like if, a when is a statement and has no value. Like if, it pairs well with give back. In order_for, each arm gives back a word, and because the _ arm covers everything else, the compiler accepts that every path through the task gives something back.

repeat ... times#

The simplest loop runs a block a set number of times.

examples/book_repeat_times.fk compilesruns
-- repeat N times
task main() {
    repeat 3 times {
        say "Valkyries, sound off"
    }

    -- A bare `repeat N times` has no counter. Keep one yourself, or write
    -- `repeat N times with i` for one that starts at 0.
    pilot mut number = 1
    repeat 3 times {
        say "Valkyrie-{number}"
        number += 1
    }

    -- The count is worked out once, before the first pass.
    pilot mut passes = 2
    repeat passes times {
        passes += 10
        say "pass (passes is now {passes})"
    }

    -- Zero or a negative count means the body never runs.
    repeat 0 times { say "never printed" }
}
Program output
Valkyries, sound off
Valkyries, sound off
Valkyries, sound off
Valkyrie-1
Valkyrie-2
Valkyrie-3
pass (passes is now 12)
pass (passes is now 22)

The count is any int: a literal, a pilot, a calculation. It is worked out once, before the first pass. The third loop in the listing changes passes inside its own body, and still makes exactly the two passes it started with.

The loop has no counter of its own. If the body needs to know which pass it is on, keep a pilot beside the loop and increase it yourself, as the second loop does, or let the loop count for you with repeat N times with i, shown later in this chapter.

A count of zero, or a negative count, means the body does not run at all.

The count must be an int. A num is refused even if its value is whole:

diagnostics/v3/book_repeat_needs_int.fk does not compile
task main() {
    repeat 2.5 times {
        say "again"
    }
}
Compiler diagnostic
type error: repeat count must have type int, got num (line 2)
 --> book_repeat_needs_int.fk:2:1
2 |     repeat 2.5 times {
  | ^

repeat until#

When you do not know the number of passes in advance, loop on a condition.

examples/book_repeat_until.fk compilesruns
-- repeat until CONDITION: the test comes first, and the loop stops
-- when the condition is true.
task main() {
    pilot mut countdown = 3
    repeat until countdown == 0 {
        say countdown
        countdown -= 1
    }
    say "launch"

    -- Already true: the body does not run even once.
    pilot mut laps = 0
    repeat until true {
        laps += 1
    }
    say laps

    -- How many times can 1000 be halved before it reaches 1?
    pilot mut value = 1000
    pilot mut halvings = 0
    repeat until value <= 1 {
        value = value / 2
        halvings += 1
    }
    say halvings
}
Program output
3
2
1
launch
0
9

Two things about repeat until are easy to get backwards.

The loop runs while the condition is false. until gives the condition for stopping. repeat until countdown == 0 keeps going as long as countdown is not zero. If you are used to while loops, this is the opposite sense. FREAK has a while as well, described below, so you can pick whichever reads better.

The condition is tested before each pass, including the first. If it is already true, the body never runs. The second loop in the listing shows this: laps is still zero afterwards.

For the loop to end, something in the body has to move the condition toward true. In the third loop, value is halved on every pass, so sooner or later it reaches 1.

A loop with no end#

Sometimes the natural place to stop is in the middle of the body. Then write a condition that is never true, and leave with break:

examples/book_loop_forever.fk compilesruns
-- A loop with no natural end, left with `break`.
task main() {
    pilot mut altitude = 0

    repeat until false {
        altitude += 2500
        say "climbing through {altitude}"
        if altitude >= 10000 { break }
    }

    say "level at {altitude}"
}
Program output
climbing through 2500
climbing through 5000
climbing through 7500
climbing through 10000
level at 10000

repeat until false loops forever by itself. The if inside decides when to stop. This is useful when the decision depends on something the body has just done, and you saw it in Chapter 2, where the game loop was left from the middle when the input ran out.

training arc#

A loop that depends on a condition can fail to end. If the body never makes the condition true, the program hangs. FREAK has a loop that rules this out.

examples/book_training_arc.fk compilesruns
-- training arc: a condition loop with a compulsory upper bound.
task main() {
    -- Stops when the condition becomes true...
    pilot mut power = 0
    training arc until power >= 100 max 8 sessions {
        power += 20
    }
    say power

    -- ...or when the sessions run out, whichever comes first.
    pilot mut stuck = 0
    training arc until stuck > 1000 max 4 sessions {
        stuck += 1
    }
    say stuck
}
Program output
100
4

A training arc is a repeat until with a limit. After max you give the largest number of passes, called sessions, that the loop may make. The loop ends when the condition becomes true or when the sessions are used up, whichever happens first.

In the first loop the condition wins: power reaches 100 on the fifth session and the loop stops with three sessions unused. In the second the limit wins: the condition would need more than a thousand sessions and the loop is allowed four.

Like repeat until, the condition is tested before each session. The limit can be any int expression.

After a training arc you often want to know which of the two ended it. Test the condition again:

examples/book_collatz.fk compilesruns
-- collatz.fk: how many steps does a number take to reach 1?
-- Halve it when it is even; otherwise triple it and add one.
task collatz_steps(start: int) -> int {
    pilot mut n = start
    pilot mut steps = 0

    training arc until n == 1 max 1000 sessions {
        if n % 2 == 0 {
            n = n / 2
        } else {
            n = 3 * n + 1
        }
        steps += 1
    }

    if n != 1 { give back -1 }         -- ran out of sessions
    give back steps
}

task main() {
    pilot mut start = 1
    pilot mut longest = 0
    pilot mut longest_start = 1

    repeat 30 times {
        pilot steps = collatz_steps(start)
        if steps > longest {
            longest = steps
            longest_start = start
        }
        start += 1
    }

    pilot six = collatz_steps(6)
    pilot twenty_seven = collatz_steps(27)
    say "6 takes {six} steps"
    say "27 takes {twenty_seven} steps"
    say "below 31, the longest chain starts at {longest_start} ({longest} steps)"
}
Program output
6 takes 8 steps
27 takes 111 steps
below 31, the longest chain starts at 27 (111 steps)

collatz_steps follows a famous rule: halve an even number, triple an odd one and add one, and count how many steps it takes to arrive at 1. Nobody has proved that every starting number gets there, so a plain repeat until n == 1 would be a loop with no guarantee. The training arc gives it one. If a thousand sessions pass without reaching 1, the loop ends anyway, the test after it sees that n is not 1, and the task gives back -1 to say that it gave up.

Use a training arc whenever a loop's end depends on something you cannot fully control: a search that might not converge, a retry that might never succeed, input that might never be valid.

for each#

The last loop visits the elements of a list, one after another. Lists are the subject of a chapter in Part II. For now it is enough to know that square brackets around values separated by commas make one.

examples/book_for_each.fk compilesruns
-- for each: visit every element of a list, in order.
task main() {
    pilot squadron = ["Takeru", "Meiya", "Sumika"]

    for each name in squadron {
        say "{name}, report in"
    }

    -- The loop gives you the element, not its position. Count it yourself.
    pilot mut position = 1
    for each name in squadron {
        say "{position}. {name}"
        position += 1
    }

    -- break and continue work here too.
    pilot readings = [12, 0, 7, 0, 30, 99]
    pilot mut total = 0
    for each reading in readings {
        if reading == 0 { continue }
        if reading > 50 { break }
        total += reading
    }
    say total
}
Program output
Takeru, report in
Meiya, report in
Sumika, report in
1. Takeru
2. Meiya
3. Sumika
49

After for each comes a name, then in, then the list. On each pass the name holds the next element. It exists only inside the body, like any pilot declared in a block.

for each gives you the element and not its position. When you need both, count alongside the loop, as the second loop in the listing does.

The thing after in has to be a list or a range. A word is not a list of its letters:

diagnostics/v3/book_for_each_word.fk does not compile
task main() {
    for each letter in "Takeru" {
        say letter
    }
}
Compiler diagnostic
type error: for each requires a list with a known element type (line 2)
 --> book_for_each_word.fk:2:1
2 |     for each letter in "Takeru" {
  | ^

Counting loops#

When you want the numbers themselves, loop over a range, or use a loop that counts.

examples/book_for_ranges.fk compilesruns
-- Counting loops. A range a..b stops before b; a..=b includes b.
task main() {
    for each i in 1..4 {
        say "range {i}"
    }
    for each i in 1..=3 {
        say "inclusive {i}"
    }
    for each i in 0..10 step 4 {
        say "step {i}"
    }
    repeat 2 times with pass {
        say "pass {pass}"
    }
    for (pilot i = 3; i > 0; i -= 1) {
        say "countdown {i}"
    }
}
Program output
range 1
range 2
range 3
inclusive 1
inclusive 2
inclusive 3
step 0
step 4
step 8
pass 0
pass 1
countdown 3
countdown 2
countdown 1

There are three forms, and they mean the same as they would in most languages. Use whichever reads best for the loop at hand.

  • for each i in 1..4 gives i the values 1, 2 and 3. The end of a range written .. is not included. Written ..=, as in 1..=3, it is.
  • step sets the distance between values: 0..10 step 4 gives 0, 4 and 8.
  • repeat 2 times with pass is a repeat ... times loop that names its counter. The counter starts at 0.
  • for (pilot i = 3; i > 0; i -= 1) is the form from C and its relatives: a pilot to start with, a condition tested before each pass, and a statement run after each pass.

In every form the counter is a pilot that exists only inside the loop.

while#

while is repeat until turned the other way round: it runs as long as its condition is true. repeat while is another spelling of the same loop.

examples/book_while.fk compilesruns
-- `while` runs as long as its condition is true: the opposite of `repeat until`.
task main() {
    pilot fuel = 3
    while fuel > 0 {
        say "fuel {fuel}"
        fuel -= 1
    }

    pilot laps = 0
    repeat while laps < 2 {
        laps += 1
    }
    say "laps {laps}"
}
Program output
fuel 3
fuel 2
fuel 1
laps 2

Like repeat until, while tests its condition before each pass, so a condition that is false at the start means the body never runs. while is a reserved word, so it cannot be used as a name.

break and continue#

Two statements change the course of any loop from inside its body.

examples/book_break_continue.fk compilesruns
-- break leaves the loop; continue skips to its next pass.
task main() {
    pilot mut n = 0
    repeat 10 times {
        n += 1
        if n % 2 == 0 { continue }     -- skip the even numbers
        if n > 7 { break }             -- and stop after 7
        say n
    }
    say "stopped at {n}"

    -- In nested loops, both act on the innermost loop only.
    pilot mut row = 1
    repeat 3 times {
        pilot mut column = 1
        repeat 3 times {
            if column > row { break }
            say "{row},{column}"
            column += 1
        }
        row += 1
    }
}
Program output
1
3
5
7
stopped at 9
1,1
2,1
2,2
3,1
3,2
3,3

break ends the loop. Execution carries on with the first statement after it.

continue ends the current pass. In a repeat ... times loop the next pass begins; in a repeat until, a while or a training arc the condition is tested again; in a for each the loop moves to the next element or value.

When loops are nested, break and continue act on the innermost loop that contains them. The second half of the listing prints a triangle: the inner loop is cut short by break when column passes row, and the outer loop carries on to the next row. There is no way to break out of two loops at once. If you need to, put the loops in a task and use give back, which leaves the whole task from any depth.

Be careful with continue in a loop that keeps its own counter. If the counter is increased at the bottom of the body, continue skips the increase. In the listing, n += 1 is the first statement of the body for exactly this reason.

Putting it together#

Here is a small program that uses a counted loop, a task that decides with if, and the conversion from Chapter 3.

examples/book_fizzbuzz.fk compilesruns
-- fizzbuzz.fk: count from 1 to 15. Multiples of 3 say Fizz, multiples of 5
-- say Buzz, and multiples of both say FizzBuzz.
task label(n: int) -> word {
    if n % 15 == 0 { give back "FizzBuzz" }
    if n % 3 == 0 { give back "Fizz" }
    if n % 5 == 0 { give back "Buzz" }
    give back n.to_word()
}

task main() {
    pilot mut n = 1
    repeat 15 times {
        say label(n)
        n += 1
    }
}
Program output
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz

label tests the most specific case first. A multiple of 15 is also a multiple of 3 and of 5, so if those tests came first, FizzBuzz would never be given back. Order matters in a chain of tests, as it did at the start of this chapter.

The task gives back a word in every case, so the number has to be converted with to_word(). All four give back statements must agree on the type.

In V4#

At the pinned commit V4 runs if, when on ints, repeat ... times, repeat until, break and continue. These two listings give the same results on both compilers:

examples-v4/loops.fk V4 snapshot 7c9a1f3c27runs
-- Three of V3's loops, with break and continue, on V4.
task main() -> int {
    pilot total = 0

    repeat 4 times { total += 10 }              -- 40

    pilot n = 0
    repeat until n >= 6 {
        n += 1
        if n == 2 { continue }                  -- skips 2
        if n == 5 { break }                     -- stops before 5
        total += n                              -- adds 1, 3, 4
    }

    pilot drills = 0
    training arc until drills >= 3 max 10 sessions { drills += 1 }

    give back total + drills                    -- 40 + 8 + 3
}
Exit code 51
examples-v4/when_int.fk V4 snapshot 7c9a1f3c27runs
task order_code(code: int) -> int {
    pilot answer = 0
    when code {
        1 -> answer = 10
        2 -> answer = 20
        _ -> answer = 99
    }
    give back answer
}

task main() -> int {
    give back order_code(2) + order_code(7)
}
Exit code 119

It checks conditions as strictly as V3:

examples-v4/if_needs_bool.fk V4 snapshot 7c9a1f3c27rejected
task main() -> int {
    pilot fuel = 40
    if fuel { give back 1 }
    give back 0
}
V4 diagnostic
2|0@45:68|if condition must be bool|got int

The rest of this section is what does not match.

A chain in which every branch gives back does not build. This is the shape of status in this chapter's first listing: an if, its else if branches and an else, each ending in give back.

examples-v4/else_if_give_back.fk V4 snapshot 7c9a1f3c27rejected
-- Every branch gives back, so nothing follows the chain.
task sign(n: int) -> int {
    if n > 0 {
        give back 1
    } else if n < 0 {
        give back 2
    } else {
        give back 3
    }
}

task main() -> int {
    give back sign(5)
}
V4 diagnostic
2|0@58:202|mir cfg unreachable live block|sign block 5 has no predecessors but still carries live statements or control flow

The message comes from inside the compiler, and it is a defect in this commit, not a rule of the language. Until it is fixed, let the last case follow the chain instead of sitting in an else:

examples-v4/else_if_trailing.fk V4 snapshot 7c9a1f3c27runs
-- The same decision, with the last case after the chain.
task sign(n: int) -> int {
    if n > 0 {
        give back 1
    } else if n < 0 {
        give back 2
    }
    give back 3
}

task main() -> int {
    give back sign(5) * 100 + sign(0 - 5) * 10 + sign(0)
}
Exit code 123

**An arm after _ is an error.** V3 refuses it, as shown earlier in this chapter. V4 refuses the program too, with its own message:

examples-v4/when_default_first.fk V4 snapshot 7c9a1f3c27rejected
task main() -> int {
    pilot code = 0
    pilot rank = 5
    when rank {
        _ -> code += 2
        5 -> code += 4
    }
    give back code
}
V4 diagnostic
2|0@106:120|unreachable when arm|an earlier arm already matches every remaining value
2|0@0:147|mir cfg unreachable live block|main block 3 has no predecessors but still carries live statements or control flow

The first line is the useful one. The second is the compiler tripping over the same arm again.

**for each is understood and cannot be built yet.** V4 reads the loop and checks it. The stage that generates code has no lists yet:

examples-v4/for_each.fk V4 snapshot 7c9a1f3c27rejected
task main() -> int {
    pilot total = 0
    for each reading in [12, 7, 30] {
        total += reading
    }
    give back total
}
V4 diagnostic
v4-native-contract-error=native rvalue not yet supported: List

**The session limit of a training arc is not enforced.**

examples-v4/training_arc_cap.fk V4 snapshot 7c9a1f3c27runs
-- The condition here needs eleven passes; the cap allows three.
-- V3 stops at 3. This V4 snapshot checks the cap's type but does not
-- enforce it, so the loop runs until the condition is true.
task main() -> int {
    pilot drills = 0
    training arc until drills > 10 max 3 sessions {
        drills += 1
    }
    give back drills
}
Exit code 11
Careful

This is the serious one, because nothing reports it. V3 stops this loop after three sessions and gives 3. At the pinned commit V4 checks that the limit is a number and then builds an ordinary condition loop, which runs until drills exceeds 10. A training arc whose condition can never become true does not end in this V4 commit. That is the one situation the loop exists to prevent.

**V4 has with growth.** The specification lets a training arc heading end with with growth, which asks the compiler to verify that the body really does change the pilot named in the condition. V3 does not accept it. This V4 commit does:

examples-v4/with_growth.fk V4 snapshot 7c9a1f3c27runs
-- `with growth`: the body must change the pilot the condition tests.
task main() -> int {
    pilot power = 0
    training arc until power >= 100 max 8 sessions with growth {
        power += 20
    }
    give back power
}
Exit code 100

And it refuses a loop whose body cannot make progress:

examples-v4/with_growth_stalled.fk V4 snapshot 7c9a1f3c27rejected
task main() -> int {
    pilot power = 0
    pilot fatigue = 0
    training arc until power >= 100 max 8 sessions with growth {
        fatigue += 20
    }
    give back fatigue
}
V4 diagnostic
2|0@67:155|training arc with growth must mutate condition subject|Yuuko found no progress for power

The name in that message, Yuuko, is one of the voices the specification gives to the compiler's diagnostics. Part VII covers them.

Planned#

Planned

In the specification, when is much more than a comparison with literals. Its arms are patterns that can take a value apart: match one variant of a type with several forms and name the fields inside it, in one step. The compiler is also to check that the arms cover every possibility. That depends on variants, which V3 does not have. Part VII covers both.

Planned

The specification lets for each deliver the position along with the element, written for each (i, item) in list.enumerate(). Until a compiler has it, count with a pilot beside the loop.

Summary#

if chooses on a condition, which must be a bool. else if and else extend it, and the first true branch wins. when chooses by comparing one value with literal arms; put _ last to catch everything else. Both are statements, not values.

repeat N times runs a fixed number of passes, counted once at the start, and can name its counter with with. repeat until runs while its condition is false and while runs while it is true; both test before each pass. training arc is repeat until with a compulsory limit on the number of passes, so it always ends. for each visits each element of a list or each value of a range, and the C-style for counts with a start, a condition and a step.

break leaves the innermost loop and continue skips to its next pass.

That completes the core of the language. With pilots, tasks and control flow you can write any computation. What you cannot do yet is hold more than one value at a time, or describe data of your own. Part II begins with the type you have used most and understood least: the word.

Badged executable examples were compiled and run with the released freak 0.15.1 (Maverick). 84/84 examples match their reviewed expected output. Illustrative snippets are labelled separately. Regenerate with python tools/refresh.py.

V4 boxes were checked against the development snapshot 7c9a1f3c27 (2026-10-04); 46/46 V4 examples match their reviewed expectation. Regenerate with python tools/verify_v4.py --checkout <Freak-lang>.