Getting Started with EK9 · Chapter 3
Making Decisions and Repeating Work
So far the programs have run straight down, top to bottom, doing the same thing every time. Real programs need to choose: they behave differently depending on the values they are given. They also need to repeat: do something many times without writing it out many times. This chapter covers both. It also shows one of EK9's less expected choices: some familiar features are left out, because clearer and safer tools take their place.
Making a choice with if
The if statement runs a block of instructions only when a condition is true.
#!ek9 defines module decisions defines program Weather() -> temperature as Integer stdout <- Stdout() jacketBelow <- 18 if temperature < jacketBelow stdout.println("Bring a jacket")
Two lines need explaining.
The line -> temperature as Integer means the program is given a temperature when it runs, so
it can react to different values. The next section shows how that works and how to run a program
with a value. For now, read it as: a temperature comes in.
The line jacketBelow <- 18 may look odd. Why not write if temperature < 18? Because EK9 will
not let you compare against a bare number like 18; you must give it a name first. The name does
real work: it says what 18 means. "Below the jacket temperature" is clearer than a lone 18
inside a condition. Two values are exempt, because they seldom hide a meaning: 0 and 1. So
countdown > 0, later in this chapter, needs no name.
Now the condition itself. temperature < jacketBelow uses the < operator to compare two
numbers. It gives back a Boolean, true or false. When the condition is true, the indented
block runs; when it is false, the block is skipped.
Note. EK9 also refuses a different mistake here. Suppose you had written
temperature <- 14and thenif temperature < 18. EK9 would not build it: the compiler can see that14is always less than18, so the answer never changes. Anifwhose answer is already known is pointless, and usually a sign of a real bug. That is why the temperature comes in from outside: so the answer depends on the value.
You can offer another path with else, and test more conditions with else if. Each number
compared against gets a name, for the same reason as before:
warm <- 25 cold <- 10 if temperature > warm stdout.println("It is warm") else if temperature < cold stdout.println("It is cold") else stdout.println("It is mild")
EK9 checks each condition from the top. The first one that is true wins: its block runs, and the
rest are skipped. If none is true, the else block runs. Exactly one of the three messages prints,
every time.
Note. EK9 writes this as
else if, two ordinary words. Some languages use one word, such aselif; in EK9 that is a syntax error.
Giving a program a value
The weather program needs a temperature. You give it to the program when you run it.
Look again at the first lines of the program:
Weather()
-> temperature as Integer
The -> means "this comes in". So temperature as Integer says: the program is given one value,
a whole number, called temperature. To run it with a temperature of 12, type this at the
command line:
ek9 Weather.ek9 12
On the command line, everything you type is text: 12 is the characters "1" and "2". The program
asked for an Integer, so EK9 turns that text into an Integer before your code runs. If you type
something that is not a whole number, EK9 stops with a message instead of running with nonsense.
The value is checked and correctly typed from the moment it arrives.
A program can ask for other types too, and EK9 converts the text you type: String, Integer,
Float, Boolean, Character, Date, Time, DateTime, Duration, Money, Dimension,
Colour, and a few more. A program could ask for -> chosenDay as Date, and you would pass a date
like 2027-03-14. (Typed as 2027-3-14, it is refused: the month and day each need two digits.)
A program can also take several values, read files, and handle options like -v; the chapter
Programs, Input and Output covers those. One value coming in is enough for now.
The same idea, spelled when
EK9 lets you write when in place of if. The two behave the same:
when temperature < jacketBelow stdout.println("Bring a jacket")
Code reads better when the word fits the meaning. if suits an either/or choice; when reads
more naturally for "whenever this is true, do this". Use whichever makes the line clearer.
Joining conditions
You can join conditions with and, or and not, which mean what they mean in English:
chilly <- 18 hot <- 26 if temperature > chilly and temperature < hot stdout.println("Pleasant")
This block runs only when both parts are true. Use or when either part is enough, and not
to reverse a condition. When a condition grows long, you may break it across several lines, lining
the parts up under the first one.
Choosing among many values with switch
A long chain of if / else if is hard to read when you are checking one value against many
possibilities. For that, use switch:
#!ek9 defines module decisions defines program //Choosing among several values with 'switch'. //One 'case' can list several values, and there is no fall-through. DayKind() -> day as Integer stdout <- Stdout() switch day case 1, 2, 3, 4, 5 stdout.println("Weekday") case 6, 7 stdout.println("Weekend") default stdout.println("Not a day of the week")
switch day looks at the value of day. Each case lists one or more values to match. The first
matching case runs, and the default case runs when nothing else matched. Run it with 6 and it
prints:
Weekend
Three things here are deliberate.
First, one case can list several values: case 1, 2, 3, 4, 5 matches any weekday, so you do
not need five separate cases.
Second, there is no "fall-through". In some older languages, a switch keeps running into the
next case unless you add a word (break) to stop it, and forgetting that word is a classic bug,
recorded both in the CERT C coding standard (recommendation MSC17-C) and in the weakness list
CWE-484, "Omitted Break Statement in Switch". In EK9 the matching case runs, and that is all.
Third, the default is required. Every switch must end with one, so there is always an
answer for a value no case listed. Leave it out and the program does not build
(E07320: 'default' is required in this 'switch' statement). No unhandled value slips through
unnoticed.
Cases can compare, not just match
So far each case has listed exact values. case 6, 7 means "is the value 6, or is it 7?" —
a test of equality, and often all you need.
A case can also compare, using <, > or any of EK9's comparisons:
#!ek9 defines module decisions defines program Grade() -> score as Integer stdout <- Stdout() passMark <- 40 meritMark <- 60 distinctionMark <- 75 switch score case < passMark stdout.println("Fail") case < meritMark stdout.println("Pass") case < distinctionMark stdout.println("Merit") default stdout.println("Distinction")
Run it with a score of 55:
Pass
Here order matters. A score of 55 is below 60 and below 75, so two cases are true at
once. EK9 checks the cases from the top down, and the first one that matches wins; nothing
below it is looked at. 55 fails the first test (< 40), passes the second (< 60), and stops
there, printing Pass.
So write comparison cases from the lowest boundary upward, letting each case assume the ones
above it have already ruled out the lower values. Put < 75 first and every passing score would
match it; nobody would ever see Merit or Distinction. The order is part of the meaning.
So switch is two tools under one word. With value cases (case 6, 7) it is a lookup where
order does not matter, because a value can equal only one thing. With comparison cases
(case < 60) it is a chained if / else if, checked top to bottom, where order is
everything. You can mix the two in one switch. In both, default is the final else: the
catch-all that runs when no case above it matched.
Note. Because equality is the most common test, a bare
case 6is shorthand forcase == 6. You can write the==out when it makes the intent clearer.
Coming later. Here the switch is on plain numbers. Often you are really choosing among a small, fixed set of named things — the suits in a deck of cards, the days of the week, the states of a traffic light. EK9 lets you define such a set, called an enumeration, and
switchworks well with one. The chapter Records and Enumerations brings thisswitchback with real names likeMondayin place of1.
Repeating with a range
To do something a fixed number of times, count over a range with for:
#!ek9 defines module repetition defines program Counting() stdout <- Stdout() for i in 1 ... 10 stdout.println(`Value [${i}]`)
1 ... 10 is a range from 1 to 10, including both ends. The loop runs its block once for each
value, and each time the name i holds the current number. This prints Value [1] through
Value [10], ten lines. The loop makes i itself; you do not declare it.
You can change the step with by:
for i in 0 ... 10 by 2 stdout.println(`Even [${i}]`)
This counts 0, 2, 4, 6, 8, 10. A negative step counts down:
for i in 10 ... 0 by -2 stdout.println(`Down [${i}]`)
Repeating while something is true
Sometimes you do not know how many times to repeat; you only know the condition for carrying on.
Then use while:
countdown <- 5 while countdown > 0 stdout.println(`${countdown}...`) countdown := countdown - 1 stdout.println("Lift off")
The while block runs as long as its condition is true. Each pass prints the number, then makes
countdown one smaller with :=. After five passes countdown > 0 is false, and the loop stops.
Running at least once: do / while
A plain while checks its condition first, so if the condition is false at the start, the
block never runs. When the block should run once before the check, use do / while:
#!ek9 defines module repetition.countup defines program //'do ... while' runs the block once, then keeps going while the test is true. Countup() -> stopAt as Integer stdout <- Stdout() n <- 1 do stdout.println(`${n}`) n++ //short for n := n + 1 while n <= stopAt
The do block always runs at least once. Then the while line is checked, and the block repeats
as long as it is true. Run it with a stopAt of 3 and it prints:
1
2
3
stopAt is a value the program is given when it runs, like the temperature earlier. n++ is
short for n := n + 1, "add one to n"; n-- takes one away. The only difference from a plain
while is when the check happens: while checks before the first run, do / while after it.
The features that are not here
Many languages have three instructions for jumping out of the middle of code: break, to leave
a loop early; continue, to skip to the next pass; and return, to leave a block of code partway
through. EK9 has none of them. This is deliberate.
The reason is evidence, not taste. Jumps out of the middle make it hard to see, from the shape of the code, which lines will run — the argument Edsger Dijkstra made against unstructured jumps in 1968.1 The results are loops that exit at the wrong moment, checks that get skipped, and security holes from leaving a block earlier than intended. One well-known case, "goto fail", skipped a security check on Apple's iOS and macOS because of a single stray jump.2 Rather than warn you about misusing these tools, EK9 removes them: the idea from Hello, EK9, applied to control flow.
What you use instead are tools that say what you mean:
- Instead of looping and
break-ing out once you have what you need, you describe the result you want, and EK9 stops at the right point. - Instead of
continue-ing past items you do not want, you filter them out before the loop sees them. - Instead of
return-ing a value from the middle of a block, you name the result up front, and the compiler checks that every path through your code sets it.
The first two replacements come from EK9's stream pipelines, which process a collection of data in a line that reads almost like a sentence:
// cat = "take each item", filter = "keep the ones that match" // cat readings | filter by inComfortRange | collect as List of Integer
Pipelines have a chapter of their own. Nothing was taken away without something clearer put in its place.
A map of what is coming
You now have the everyday tools for choosing and repeating. EK9 has a few more ways to control what happens and in what order. They work differently, and each gets its own chapter, but it helps to know they exist:
- Stream pipelines control flow over a whole collection of data. Instead of a loop, you write
a line like
cat items | filter by ... | map with ..., and the data flows through it. This is how EK9 replaces loops withbreakandcontinue. (Streams and Pipelines.) - Exceptions control flow when something goes wrong. A
tryblock runs your code, and acatchblock takes over if it fails. (Errors and Exceptions, in the second volume, EK9 in Depth.) - Guards take a value and check it is really there, in one step. The same pattern works
inside
if,while,forandswitch. (Guards and Safe Code.) - Expression forms let a choice or a loop hand back a value. A
switch, aforor awhilecan produce a result you store in a variable. Anifcannot; a simple either/or uses a short form called the ternary, first used in Speaking Every Language. (Guards and Safe Code.)
Try it yourself
- Write a program that prints whether a number is negative, zero, or positive. Use
if/else if/else. (The number must come in to the program, and any value you compare against needs a name, except0and1.) - Use a
switchto turn a month number (1–12) into its season. Group the months with multi-value cases, and usedefaultfor anything out of range. - Use a
forloop to print the two-times table, from2to20. - Rewrite exercise 3 as a
whileloop, then as ado/whileloop. Which reads most clearly to you, and why?
Recap
You can make decisions with if, else if, else and when, join conditions with and, or
and not, and choose among many values with switch: several values per case, comparison cases
like case < 40, first match wins, and no fall-through. You can repeat work with for over a
range, with while, and with do / while when the block must run at least once. EK9 has no
break, continue or return, and you have seen what replaces them. And you have a map of the
larger control-flow ideas to come: stream pipelines, exceptions, guards and expression forms.
Remove the sharp edge, supply a better tool: that pattern recurs throughout EK9. Functions are next, so you can name and reuse pieces of logic instead of repeating them.
-
Edsger W. Dijkstra, "Go To Statement Considered Harmful", Communications of the ACM, 1968 — the classic argument for structured control flow over unstructured jumps. See the evidence appendix for plain-language notes. ↩
-
The Apple "goto fail" bug (2014, CVE-2014-1266): a duplicated
goto fail;line skipped TLS signature verification on iOS and macOS, weakening secure connections. It is one of the clearest real-world arguments for removing mid-block jumps from a language. ↩
In the EK9 reference: Design note: why EK9 has no break, continue or return, Design note: what EK9 leaves out, Flow Control in EK9