Beginner

Control Flow in Icon

Master conditionals, loops, and case expressions in Icon, where goal-directed evaluation makes success and failure the engine of control flow

Control flow determines the order in which your program’s statements run. Most languages build this on top of boolean values: a condition is either true or false, and that decides which branch executes. Icon takes a fundamentally different approach. Here, expressions either succeed (producing a value) or fail (producing no value at all), and it is this success/failure outcome — not a boolean — that drives every conditional and loop.

This is Icon’s goal-directed evaluation, and it reshapes how control structures feel. An if statement runs its then branch when the condition succeeds, regardless of what value the condition produced. A comparison like 0 < n doesn’t return true; it succeeds and yields n. This subtle shift unlocks expressive idioms like chained comparisons and conditions built from generators.

In this tutorial you’ll learn Icon’s conditional expressions, the case expression, its family of loops (while, until, repeat, and the generator-driven every), and the loop-control words break and next. Along the way you’ll see how failure — far from being an error — is the quiet mechanism behind Icon’s elegant control flow.

Conditionals and Goal-Directed Evaluation

Icon’s if/then/else looks familiar, but remember: the condition runs its then branch when it succeeds, not when it returns true. Comparison operators succeed or fail, and on success they return their right-hand operand — which is what makes chained comparisons like 0 < n < 10 work.

Create a file named conditionals.icn:

procedure main()
    n := 7

    # Basic if/else: the then-branch runs when the condition succeeds
    if n > 0 then
        write(n, " is positive")
    else
        write(n, " is not positive")

    # Arithmetic binds tighter than comparison: (n % 2) = 0
    if n % 2 = 0 then
        write(n, " is even")
    else
        write(n, " is odd")

    # Chained comparison: 0 < n succeeds and yields n, then n < 10 runs
    if 0 < n < 10 then
        write(n, " is a single digit")
end

The expression 0 < n succeeds and produces 7; that value flows into 7 < 10, which also succeeds. There is no boolean in sight — just a chain of expressions, each succeeding and passing its value along.

The Case Expression

When you need to branch on a single value across many possibilities, the case expression is cleaner than a ladder of if/else. Each clause pairs a label with an action, and a default clause handles anything unmatched.

Create a file named case_expr.icn:

procedure main()
    s := "programming"
    vowels := 0
    consonants := 0

    # !s generates each character of the string in turn
    every c := !s do {
        case c of {
            "a": vowels +:= 1
            "e": vowels +:= 1
            "i": vowels +:= 1
            "o": vowels +:= 1
            "u": vowels +:= 1
            default: consonants +:= 1
        }
    }

    write("Letters:    ", *s)
    write("Vowels:     ", vowels)
    write("Consonants: ", consonants)
end

Here *s is the size operator (the length of the string), and +:= is augmented assignment — vowels +:= 1 adds one to vowels. The case value is compared against each label until one matches; if none does, the default action runs.

Loops: while, every, and repeat

Icon offers several looping constructs. A while loop repeats as long as its control expression succeeds. The every expression is Icon’s signature loop: it drives an action across all values a generator can produce — 1 to 5 generates each integer in the range. A repeat loop runs forever until you break out of it.

Create a file named loops.icn:

procedure main()
    # while: repeats while the condition succeeds
    i := 1
    while i <= 5 do {
        writes(i, " ")
        i +:= 1
    }
    write()

    # every: iterate over all values of the generator 1 to 5
    every j := 1 to 5 do
        writes(j * j, " ")
    write()

    # repeat: an infinite loop exited with break
    k := 0
    repeat {
        k +:= 1
        if k > 3 then break
        writes(k, " ")
    }
    write()
end

writes() prints its arguments without a trailing newline (unlike write()), and a bare write() emits just a newline to end each line. The every loop is what sets Icon apart: rather than manually advancing a counter, you let the generator 1 to 5 supply every value, and every makes sure the loop body runs for each one.

Loop Control: next and break

Inside any loop you can skip the rest of the current iteration with next (like continue in C-family languages) or exit the loop entirely with break. Generators also accept a step with by, so 1 to 10 by 2 walks through the odd numbers.

Create a file named loop_control.icn:

procedure main()
    # Step a range with 'by'
    every n := 1 to 10 by 2 do
        writes(n, " ")
    write()

    # next skips an iteration; break ends the loop
    every n := 1 to 20 do {
        if n % 3 ~= 0 then next    # skip values that are not multiples of 3
        if n > 15 then break       # stop once we pass 15
        writes(n, " ")
    }
    write()
end

The operator ~= is numeric “not equal.” In the second loop, next causes Icon to abandon the current iteration and ask the generator for its next value, while break abandons the loop completely once n exceeds 15.

Failure and Generators as Control Flow

This is where Icon’s philosophy shines. Conditions don’t have to be comparisons — any expression that can succeed or fail can drive control. The find() function fails when its target isn’t present, and that failure naturally selects the else branch. Alternation (|) turns a condition into a small generator, and because if/then/else is itself an expression, you can assign its result directly.

Create a file named goal_directed.icn:

procedure main()
    grade := 85

    # Alternation: the condition succeeds if grade equals any listed value
    if grade = (85 | 90 | 95) then
        write("Honor roll score!")

    # find() succeeds (returns a position) or fails — that drives the branch
    sentence := "the quick brown fox"
    if find("quick", sentence) then
        write("Found 'quick' in the sentence")

    # Failure is normal, not an error: the else runs because find fails
    if find("lazy", sentence) then
        write("Found 'lazy'")
    else
        write("'lazy' is not here")

    # if/then/else is an expression — assign its produced value
    n := 42
    parity := if n % 2 = 0 then "even" else "odd"
    write(n, " is ", parity)
end

The alternation 85 | 90 | 95 is a generator: Icon tries grade = 85, which succeeds immediately, so the whole condition succeeds. When find("lazy", ...) fails, Icon doesn’t raise an error — failure is an ordinary outcome that simply steers execution to the else. This unified treatment of success and failure is what makes Icon’s control flow distinctive.

Running with Docker

You can compile and run every example with the official Icon image — no local install required.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Pull the official image
docker pull codearchaeology/icon:latest

# Compile and run the conditionals example
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont conditionals.icn && ./conditionals"

# Compile and run the case expression example
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont case_expr.icn && ./case_expr"

# Compile and run the loops example
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont loops.icn && ./loops"

# Compile and run the loop-control example
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont loop_control.icn && ./loop_control"

# Compile and run the goal-directed example
docker run --rm -v $(pwd):/app -w /app codearchaeology/icon:latest sh -c "icont goal_directed.icn && ./goal_directed"

The icont translator turns each .icn source file into a standalone executable named after the file (without the extension), which you then run directly.

Expected Output

Running conditionals.icn:

7 is positive
7 is odd
7 is a single digit

Running case_expr.icn:

Letters:    11
Vowels:     3
Consonants: 8

Running loops.icn:

1 2 3 4 5 
1 4 9 16 25 
1 2 3 

Running loop_control.icn:

1 3 5 7 9 
3 6 9 12 15 

Running goal_directed.icn:

Honor roll score!
Found 'quick' in the sentence
'lazy' is not here
42 is even

Key Concepts

  • Success and failure, not booleans — Icon’s conditionals branch on whether an expression succeeds (produces a value) or fails (produces none); there is no true/false involved.
  • Comparisons return values0 < n succeeds and yields n, which is why chained comparisons like 0 < n < 10 work naturally.
  • every is the generator loop — it runs its body once for every value a generator (such as 1 to 10) can produce, replacing manual counter management.
  • while and until loop on success/failure of a condition; repeat loops forever until a break.
  • next and break skip the current iteration or exit the loop, just like continue and break elsewhere.
  • Ranges with by1 to 10 by 2 is a generator that steps through values, perfect for every loops.
  • case expressions branch on a single value with multiple labels and an optional default.
  • Failure is not an error — functions like find() fail when there’s no result, and that failure cleanly drives the else branch instead of crashing the program.

Running Today

All examples can be run using Docker:

docker pull codearchaeology/icon:latest
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining