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.
| |
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/falseinvolved. - Comparisons return values —
0 < nsucceeds and yieldsn, which is why chained comparisons like0 < n < 10work naturally. everyis the generator loop — it runs its body once for every value a generator (such as1 to 10) can produce, replacing manual counter management.whileanduntilloop on success/failure of a condition;repeatloops forever until abreak.nextandbreakskip the current iteration or exit the loop, just likecontinueandbreakelsewhere.- Ranges with
by—1 to 10 by 2is a generator that steps through values, perfect foreveryloops. caseexpressions branch on a single value with multiple labels and an optionaldefault.- Failure is not an error — functions like
find()fail when there’s no result, and that failure cleanly drives theelsebranch instead of crashing the program.
Running Today
All examples can be run using Docker:
docker pull codearchaeology/icon:latest
Comments
Loading comments...
Leave a Comment