The latest instance of the course can be found at: O1: 2024
Luet oppimateriaalin englanninkielistä versiota. Mainitsit kuitenkin taustakyselyssä osaavasi suomea. Siksi suosittelemme, että käytät suomenkielistä versiota, joka on testatumpi ja hieman laajempi ja muutenkin mukava.
Suomenkielinen materiaali kyllä esittelee englanninkielisetkin termit.
Kieli vaihtuu A+:n sivujen yläreunan painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 8.3: Robots and Conditional Looping
About This Page
Questions Answered: How can I repeat sequence of a commands if I
can’t find a higher-order method that’s sufficiently convenient or
efficient for my needs? I’ve heard programmers talk about something
called a while
loop — what are they and does Scala have them?
How about creating different sorts of robots?
Topics: Classics from the imperative programmer’s toolkit: do
and while
loops. A class hierarchy for robots. We’ll say a bit
more about lazy-lists, too.
What Will I Do? First read, then program.
Rough Estimate of Workload:? This is one of the most time-consuming chapters in O1. Reading the text and doing the next two parts of the Robots assignment will probably take about two or three hours. The last four parts of the assignment might take another four or five hours.
Points Available: A15 + B50 + C85.
Related Modules: Robots. Plus some mini-examples in DoWhile (new).
Introduction
Recap from 6.3 and 6.4: When solving a programming problem, you may be able to find a tool at a high level of abstraction that provides a simple solution for just what you need. If you don’t, you can build a solution using lower-level tools. Those lower-level tools may not be quite as convenient to use but they are applicable to a wider range of situations; any higher-level tools you have at your disposal havbe also been built from lower-level pieces. Occasionally, it makes sense to deliberately adopt lower-level tools in order to optimize efficiency.
An Example Program
Consider a toy example: We’re writing an interactive program that prompts the user for their name. We’d like the program to check that the user actually enters a string that contains at least one character; the program should keep re-prompting the user until it receives a non-empty string.
The program should work in the text console as illustrated below. In this example run,
the user first just hits Enter
twice, then actually inputs their name:
Enter your name (at least one character, please): The name is 0 characters long. Enter your name (at least one character, please): The name is 0 characters long. Enter your name (at least one character, please): Juha The name is 4 characters long. OK. Your name is Juha.
In Chapter 7.1, we approached problems like this by thinking about a lazy-list of inputs and the operations that we applied to the list elements. Alternatively, we may think of the program in imperative terms: as a sequence of commands that adjust the program’s state step by step. Let’s formulate such a solution, first as pseudocode:
var name = "" Do the following things: name = readLine("Enter your name (at least one character, please): ") println("The name is " + name.length + (if (name.length != 1) " characters" else " character") + " long.") Finish by checking whether name equals the empty string, and if so, do these same things again. Otherwise, advance to the code that follows. println("OK. Your name is " + name + ".")
Our pseudocode bears a resemblance to an if
statement in that it checks a condition —
is the name empty? — and uses the result to decide where to proceed in the program.
The main difference is that the code is potentially executed multiple times, as long as
the condition keeps being met.
On the other hand, this program resembles a for
loop in that it too involves repetition,
a loop. However, this loop isn’t tied to a collection like the for
loops you’ve seen:
it’s formed by repeatedly checking a particular condition.
The do
Loop
Here is a Scala implementation for our pseudocode. You can also find this program inside the DoWhile module.
var name = ""
do {
name = readLine("Enter your name (at least one character, please): ")
println("The name is " + name.length + (if (name.length != 1) " characters" else " character") + "long.")
} while (name.isEmpty)
println("OK. Your name is " + name + ".")
while
with round brackets that contain a condition,
just like we’d do in an if
. This Boolean
expression is evaluated
each time the commands above have been executed.true
. In this example, the loop jumps back
to the beginning each time name
is checked and holds an empty
string.false
.Such a do
–while
loop is commonly known simply as a do
loop. Here’s a generic
outline for writing one:
(Up here, you might have some commands that precede the loop and are executed just once.) do { One or more commands that are executed at least once and whose execution is followed by checking the condition below. In case that condition is true these commands are executed again from the beginning. } while (condition for continuing) (After the condition evaluates to false the program advances to any commands that follow the loop.)
Another example of a do
loop
Study the animation below and predict the program’s behavior when prompted.
What does “while” mean exactly?
Beginners are occasionally confused by how Scala and many other programming languages use
the English word while
. Consider the program above. Since “while” means “as long as”,
one might easily think that as soon as number
reaches 13 — which isn’t less than 10 —
the loop exits and we proceed to the code that follows. However, the condition is only
checked after each complete execution of the loop body, and so the program prints 13
twice.
For the same reason, our first program reports “The name is X characters long.” also after the user has entered the final, non-empty input.
The same while
keyword also appears in another, slightly different construct:
The while
Loop
The while
loop is an alternative for the do
loop. This type of loop uses only the
while
keyword, not do
.
Here’s a do
loop, and below it, a while
loop. As you see, the two are very similar.
(Up here, you might have some commands that precede the loop and are executed just once.) do { One or more commands that are executed at least once and whose execution is followed by checking the condition below. In case that condition is true these commands are executed again from the beginning. } while (condition) (After the condition evaluates to false the program advances to any commands that follow the loop.)
do
loop (above) the while
keyword and the condition appear
at the bottom. In a while
loop (below), they are at the top.do
loop checks the condition after each
iteration of the loop body. A while
loop instead checks the
condition before every iteration.do
loop’s body always executes at least once, before checking
the condition for the first time. A while
loop checks the
condition right off the bat, and if it evaluates to false
, the
loop body is not executed at all.(Up here, you might have some commands that precede the loop and are executed just once.) while (condition) { One or more commands that are executed zero or more times, checking the condition above before each iteration. If the condition is true these commands are executed. } (After the condition evaluates to false the program advances to any commands that follow the loop.)
In many cases, there is barely any difference between a do
loop and a while
loop.
For instance, the following while
-based code produces precisely the same interaction as
our earlier do
-based program:
var name = ""
while (name.isEmpty) { // The name is initially empty so we end up running the body at least once anyway.
name = readLine("Enter your name (at least one character, please): ")
println("The name is " + name.length + (if (name.length != 1) " characters" else " character") + " long.")
}
println("OK. Your name is " + name + ".")
These two types of loop are so much alike that it is simple to rewrite any do
loop
as a while
loop and vice versa. (Optional assignment: consider how to do that.) Whenever
you’ve decided you want to use these loops, you can just pick one based on whether you
intend the body to repeat once or more (in which case you might as well pick do
) or zero
or more times (hence while
).
Practice on while
and do
Some Loop-Writing Advice
When you write a loop, take care. If you break even one of the “golden rules of looping” listed below, you’ll have a buggy program on your hands.
val word = "llama"
var index = 0
while (index < word.length) {
println("Letter: " + word(index))
index += 1
}
var
s.What happens if you overlook one of these “rules” depends on the specific loop. A fairly typical outcome is a so-called infinite loop: the computer repeats the same commands “forever”, that is, until it runs out of resources or the program run is externally interrupted.
When does an “infinite” loop stop? How can one stop it?
The alternatives are more or less the same as those in a boxing match:
- Concession: The user can interrupt the
program. How that is done depends on the
environment where the program is running.
In IntelliJ, you can click the red
Stop button. Many command-line
environments use the keyboard shortcut
Ctrl+C
. - Technical knockout: If the program keeps consuming more and more memory, it will eventually crash with an error once that resource is depleted.
- Disqualification: If you submit such a program in A+, the system will interrupt it after a while by externally terminating the process that executes your program.
- Knockout: Power off.
Of course, the main thing to do is to prevent such problems, not to react to them.
Programmer Jim was heading to the store to pick up groceries. As he was leaving the house his wife said: “While you are there, buy some milk.” Jim never came back.
—thanks to the O1 student who shared this cautionary tale
A Couple of Voluntary Exercises
Loop-writing practice
In Chapter 7.1, we wrote this little program:
def report(input: String) = "The input is " + input.length + " characters long."
def inputs = LazyList.continually( readLine("Enter some text: ") )
inputs.takeWhile( _ != "please" ).map(report).foreach(println)
Rewrite the program to use while
or do
loop instead of LazyList
. The program’s
behavior should be the same as before. Write your code in Task1.scala
in the
DoWhile module.
A+ presents the exercise submission form here.
More practice
In Task2.scala
, write a program that works in the text console as illustrated in
these example runs:
I will compute the squares of positive integers and discard other numbers. To stop, just hit Enter. Please enter the first number: 10 Its square is: 100 Another number: 0 Another number: -1 Another number: 20 Its square is: 400 Another number: 30 Its square is: 900 Another number: 0 Another number: 40 Its square is: 1600 Another number: Done. Number of discarded inputs: 3
I will compute the squares of positive integers and discard other numbers. To stop, just hit Enter. Please enter the first number: 0 Another number: 0 Another number: 0 Another number: 0 Another number: Done. Number of discarded inputs: 4
Even the first input may be empty:
I will compute the squares of positive integers and discard other numbers.
To stop, just hit Enter.
Please enter the first number:
Done.
Number of discarded inputs: 0
A+ presents the exercise submission form here.
do
and while
vs. Higher-Level Tools
Comparing solutions
These two snippets do essentially the same thing: multiply some integers by two and find the first one that fulfills a particular condition:
val result = LazyList.from(0).map( _ * 2 ).dropWhile( _ <= 20 ).head
val result = {
var number = 0
var doubled = 0
while (doubled <= 20) {
number += 1
doubled = number * 2
}
doubled
}
LazyList
solution, the function we pass to dropWhile
has the same purpose as the conditional expression in the
while
loop.Here’s a toy function that we’ll need for our next example:
def examine(number: Int): Boolean = { println("I’m examining the number " + number + ". Is it over 90?") number > 90 }
The next two snippets generate some random numbers until they happen upon a sufficiently large random number:
LazyList.continually( Random.nextInt(100) ).map(examine).find( _ == true )
var isBig = false
do {
isBig = examine(Random.nextInt(100))
} while (!isBig)
find
call serves the same purpose
as the loop’s conditional: it governs the number of repetitions.Int
list, we'll call examine
on each number. This yields a lazy-list of Boolean
s.examine
returns true
on an element and find
therefore stops examining the lazy-list further. Once that happens,
no more random numbers or Boolean values are generated.We could have alternatively used some other method that forces the lazy-list to generate
elements until true
. Calling contains(true)
also works, for instance.
Which tools should I choose?
It should be clear by now that you can use do
and while
loops for the same purposes
as the higher-order methods that you’ve seen earlier. Often that isn’t the best idea,
but sometimes it does pay off.
By choosing to use the higher-order methods on collections, you emphasize the data that your program operates on and the operations that it performs on that data. When you use these methods, you leave the details of step-by-step execution for library methods to deal with and focus on expressing the program’s purpose. Which is nice.
By choosing to use do
and while
, you instead emphasize the step-by-step execution of
commands. These looping commands bring you a little bit closer to the low-level sequential
execution that takes place within the computer system as it runs your programs. When you
use these loops, you assume direct control of the program’s flow of execution and specify
the sequential steps of your algorithm in detail.
For many purposes, do
and while
loops are unnecessarily detailed. Generally, if you
opt for higher-order methods instead, you’ll have less work to do, you’ll write code that
is more readable, and your program is less likely to contain errors. That being said,
there are reasons to use the loops sometimes. For instance:
- Perhaps you’re implementing an algorithm that relies on modifying the program’s state step by step. Due to the nature of the algorithm, it can be natural to write it down as a sequence of consecutive steps. For instance, some programs that engage the user in dialogue in the text console arguably fall in this category.
Or:
- Perhaps you’re working on an application that needs to be highly efficient (i.e., it needs to run fast). Perhaps you’ve studied your code carefully and identified one or more subprograms that require optimization. To that end, you may wish to painstakingly detail the exact operations in those subprograms rather than leaving those minutiae to library methods.
Conditional loops such as do
and while
are available in many programming languages
and they are used widely. As you continue studying programming, you will keep running
into these constructs. In fact, they are probably used too much by many programmers.
Sometimes, people use them because the programming language affords them no alternatives;
sometimes, people use them because of the surrounding programming culture or because
of historical inertia; sometimes, people use them because they are unaware of better
alternatives.
You should know these loops. But don’t think of them as the primary or only means to implement repetition in a program. Higher-order methods, for instance, are convenient, elegant, and efficient enough for most purposes, and there are other alternatives too.
Loops as first-class citizens
Chapter 6.1 mentioned the expression “first-class functions”, which refers to the notion that a programming language treats functions like it treats other values: functions can be assigned to variables, passed as parameters, returned by functions, etc.; they are first-class citizens of the language,
Chapter 7.1 brought up lazy-lists, which enabled us to create
collections of indeterminate length and repeat operations until
a given condition was fulfilled. That is, we could use lazy-lists
for the same sorts of things that we’ve used loops for in this
chapter. On the other hand, a lazy-list — unlike a loop — can
be treated as data: you can invoke methods such as map
on a list
to produce a different list, or you can call find
, contains
,
or exists
to iterate over a part of a list. This is why some
people call lazy-lists “first-class loops”.
What about for
loops?
The code of a for
loop defines the loop’s “shape” simply and
implicitly: after each iteration of the loop body, the loop advances
towards termination by plucking the next element from the collection.
The implicit condition for continuing the loop is “as long as there
are more elements to process”.
for
loops thus operate on a higher level of abstraction than the
more explicit while
and do
loops. Scala’s for
loop is a flavor
of syntactic sugar, a different notation for a higher-order method
call.
Robots That Change Location
You’ve done the first four parts of the Robots assignment. Now’s the time for the remaining five.
Robots, Part 5 of 9: Nosebot
Implement a type of mobile robots as class Nosebot
:
Define the constructor parameters and the rest of the class header.
Write the simple
mayAdvance
method. (Remember tooverride
.)Two movement methods are missing:
moveBody
andattemptMove
. Start with the latter and use it as you implementmoveBody
. Pay attention to whatattemptMove
returns; that’s useful.A lazy-list or a loop?
You could implement
moveBody
with a loop. Alternatively, you could use a lazy-list. Can you come up with both solutions? (You don’t strictly need to, but do try.)A hint for the lazy-list solution: you can, for example, use a combination of
map
andfind
similar to the one in the random-numbers example above.Try creating some nosebots in the app’s GUI and see if they work right.
If your bot moves at double speed, check this out
In this assignment, quite a number of students end up with code that moves the nosebot forward twice, which is too fast. If that happens to you, and you can’t figure out why, see the hint below for a likely explanation.
What does this code print out?
def test(i: Int): Boolean = { println("hello") i > 0 } if (test(10)) { test(10) }Answer: it prints "hello" twice. The test function first gets called in the
if
’s condition, which causes the first "hello" to be printed andtrue
to be returned. Because of that return value, the same function then gets called again and another "hello" to appear.A very similar thing may have happened to you when you’ve called
attemptMove
.
- Notice how the program’s design made it very easy to add a new type of robot in the simulator. Basically, the only thing you needed to do was implement the algorithm that nosebots use for moving.
- Submit your solution before moving on to the next part.
A+ presents the exercise submission form here.
Robots, Part 6 of 9: collisions
Spinbot
s and Nosebot
s never collide with anything during their own turn but other
sorts of robots, such as the ones you’re about to implement in Part 8, may. Before
that, though, you’ll need to set up a few things so that Square
’s subtypes support
collisions.
- First, notice that
Wall
andFloor
are defined in the same file as their supertypeSquare
:Square.scala
. They could have been in separate files, too, but since there is little code and the classes are closely associated with each other, why not? - The
Wall
singleton’saddRobot
method doesn’t do anything yet, apart from returning a value that indicates that the arriving robot did not fit into the same square with the wall. Edit this method so that it breaks any robot attempting to enter a wall square. - Also edit the
addRobot
method in classFloor
so that it meets the specification. The method should attend to collisions between robots.
Robots, Part 7 of 9: Staggerbot
Implement Staggerbot
.
You’ll need a random-number generator (Chapter 3.6) and you’ll need to use it exactly as
specified in the Scaladocs. Create a single generator per Staggerbot
object, and pick
a random number when — and only when — the bot needs another a random direction. (If
your implementation deviates from the Scaladoc, your code will produce random numbers that
differ from what A+ expects, and you won’t score points.)
Instructions and hints:
- You’re allowed to write private methods, and we recommend that you do. For instance, you could write a separate method for picking a random direction.
- The
moveTowards
method returns a useful value. - If you fail to score points even though your bot’s movements appear
random, you’ve probably not generated the random numbers exactly
according to specification. Here are some things to check:
- When you create a random-number generator, do you pass in the appropriate seed?
- Are you creating precisely one generator per
bot? You aren’t creating a new generator every
time
moveBody
is invoked, right? Or every time a new random number is generated? - Are you generating only those numbers that are actually needed? That is, one for the bot’s direction of movement, and maybe — only if the movement succeeds! — another for the bot’s spinning direction?
- Did you generate a number in the right interval.
For example,
nextInt(10)
gives you a number between 0 and 9, inclusive.
A+ presents the exercise submission form here.
You may find it helpful to add private methods during the following steps, too.
Robots, Part 8 of 9: Lovebot
Implement Lovebot
.
A+ presents the exercise submission form here.
Robots, Part 9 of 9: Psychobot
Implement Psychobot
.
You again have a choice between a loop and/or methods on lazy-lists. Can you implement the class in different ways?
You may find the methods in GridPos
useful; check the Scaladocs.
A+ presents the exercise submission form here.
Chapter Summary
- Many programming languages have constructs known as
do
andwhile
loops; many, many programs contain such loops. These loops repeat a command or sequence of commands as long as a specific condition continues to be met.- The conditional expression is checked at the
beginning (
while
) or end (do
) of each iteration of the loop body. - When writing these loops, you must pay particular attention to initializing the loop, setting an appropriate condition for continuing, and advancing the program’s state so that the loop will eventually terminate.
- The conditional expression is checked at the
beginning (
for
loops and higher-order methods are often efficient enough and quite a bit more convenient thando
orwhile
loops.- Links to the glossary: loop,
do
loop,while
loop, iteration; lazy-list; level of abstraction.
Hey, What About break
and continue
? And return
?
Some readers who have earlier exposure to other programming languages will be familiar with additional commands that sometimes appear in loops. If you’re one of those readers, you may be wondering if Scala also has those commands?
See below for answers, which are educational for complete beginners, too.
Loop-breaking commands
Various programming languages have commands for exiting a loop body either by
terminating the loop altogether (break
) or by jumping back to the top for a new
iteration (continue
).
When programming in Scala, such commands are seldom used. You can handle practically any
scenario more elegantly by choosing a different approach. However, there is a break
command of sorts available in the Scala API; see elsewhere for more
information. Scala doesn’t provide a continue
command since that command is so very
seldom useful in a well-designed Scala program.
Returning a value from a loop
Various programming languages have a command that explicitly instructs the computer
to immediately terminate the ongoing function call and return a value. This command
is usually named return
. Programmers may use it to interrupt a function call during
a loop, terminating the loop at once along with the rest of the function.
Scala, too, has the return
keyword, although it is rather rarely used. In most
cases, it’s possible to find a better solution without it, although this is to an
extent a matter of taste.
A simple example of return
is shown below. This function loops through a vector and
returns the first non-empty string:
def firstNonEmpty(vector: Vector[String]): Option[String] = {
for (element <- vector) {
if (element.length > 0) {
return Some(element) // Found it. Terminate the search without considering the remaining elements.
}
}
return None
}
return
keyword.return
.return
need an explicit return type
annotation.In this ebook, we seldom use return
.
A bit more about return
Many Scala programmers look askance at return
and avoid using it.
This has to do with writing clearer code and the fact that return
isn’t strictly needed for anything.
return
commands may make it harder to follow the steps of a program
(the program’s control flow). This is a risk especially if the code
verbose, with many nested constructs. A reliance on return
may also
lead you to write such needlessly complicated code.
It’s generally considered a good programming practice to split a
program in small, cleanly delimited subprograms. If you do that,
there is usually little temptation to use return
. Moreover, many
languages, including Scala, have various elegant alternatives to the
“loop and return
” approach. These techniques include higher-order
methods that process only a part of a collection (such as find
,
takeWhile
, and exists
) as well as recursion (Chapter 12.1); they
make return
largely redundant. Higher-order methods and recursion are
particularly common in the functional programming style (Chapter 10.2)
but they aren’t unique to it.
It’s not a mortal sin to put a return
in your Scala program, especially
if your code is otherwise nicely written.
A tangent on StarCraft and return
In his blog post Whose bug is this anyway?!?, game developer Patrick Wyatt is kind enough to recount a past blunder: he didn’t initially notice a bug in the StarCraft game even though it was “trivial”. It’s a nice read.
The error involved a return
command that terminated a subprogram early
in case a particular condition was met. However, another section of
code many lines further down in the same subprogram was written without
consideration of the fact that that code won’t run at all if an early
return
had already triggered.
A particularly noteworthy thing about this cautionary tale is that the
conditional return
was followed by a long sequence of other commands
all within the same subprogram. All those commands were implicitly
dependent on the return
way above. return
is dangerous when a
program isn’t split into small enough functions.
A bit more about break
Even more Scala programmers frown at break
than at return
.
For the most part, break
has been criticized on similar grounds
as return
has. The additional complaint is this: assuming you do
wish to break out of a loop for some reason, and further assuming
your code is appropriately divided in small functions, each with
its own cleanly delimited purpose, then you don’t need break
; you
can just return
your way out of the loop.
If you feel you need break
, you should first consider if you
might be able to place your loop in a function and use return
to
terminate it. (And then you can consider whether there might be
a better alternative to return
as well.)
More kinds of loops?
Some programming languages provide a different sort of for
loop that
has separate “slots” for initializer code, the loop-controlling
condition, and the code that advances the loop towards termination. In
a Java program, for instance, you can write:
// This is Java, not Scala.
for (index = 0; index < myString.length(); index += 1) {
// Do the loop’s actual job here.
}
Scala doesn’t have that sort of for
loop. On the other hand,
Scala’s for
expressions are capable of various other tricks,
which we’ll discuss later.
Feedback
Please note that this section must be completed individually. Even if you worked on this chapter with a pair, each of you should submit the form separately.
Credits
Thousands of students have given feedback that has contributed to this ebook’s design. Thank you!
The ebook’s chapters, programming assignments, and weekly bulletins have been written in Finnish and translated into English by Juha Sorva.
The appendices (glossary, Scala reference, FAQ, etc.) are by Juha Sorva unless otherwise specified on the page.
The automatic assessment of the assignments has been developed by: (in alphabetical order) Riku Autio, Nikolas Drosdek, Joonatan Honkamaa, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, and Aleksi Vartiainen.
The illustrations at the top of each chapter, and the similar drawings elsewhere in the ebook, are the work of Christina Lassheikki.
The animations that detail the execution Scala programs have been designed by Juha Sorva and Teemu Sirkiä. Teemu Sirkiä and Riku Autio did the technical implementation, relying on Teemu’s Jsvee and Kelmu toolkits.
The other diagrams and interactive presentations in the ebook are by Juha Sorva.
The O1Library software has been developed by Aleksi Lukkarinen and Juha Sorva. Several of its key components are built upon Aleksi’s SMCL library.
The pedagogy of using O1Library for simple graphical programming (such as Pic
) is
inspired by the textbooks How to Design Programs by Flatt, Felleisen, Findler, and
Krishnamurthi and Picturing Programs by Stephen Bloch.
The course platform A+ was originally created at Aalto’s LeTech research group as a student project. The open-source project is now shepherded by the Computer Science department’s edu-tech team and hosted by the department’s IT services. Markku Riekkinen is the current lead developer; dozens of Aalto students and others have also contributed.
The A+ Courses plugin, which supports A+ and O1 in IntelliJ IDEA, is another open-source project. It was created by Nikolai Denissov, Olli Kiljunen, Nikolas Drosdek, Styliani Tsovou, Jaakko Närhi, and Paweł Stróżański with input from Juha Sorva, Otto Seppälä, Arto Hellas, and others.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
do
. Another keyword,while
, appears on the line that ends the loop. These are two of Scala’s reserved words (likeif
etc.), not methods on an object.