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 9.1: Robots and Conditional Looping
Introduction
Recap from 6.3 and 7.1: 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 have also been built from lower-level pieces. Occasionally, it makes sense to deliberately adopt lower-level tools 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. Hello!
In Chapter 7.2, 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 = ""
Check whether name equals the empty string. If so, do the following things; otherwise skip past them:
name = readLine("Enter your name (at least one character, please): ")
val len = name.length
println(s"The name is $len character${if name.length != 1 then "s" else ""} long.")
Go back to the step that checks whether name is empty.
println(s"OK. Your name is $name. Hello!")
Our pseudocode resembles 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 this
code is potentially executed multiple times: we keep going back
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.
Instead, it’s formed by repeatedly checking a particular condition.
The while
Loop
Here is a Scala implementation for our pseudocode. You can also find this program inside the WhileLoops module.
var name = ""
while name.isEmpty do
name = readLine("Enter your name (at least one character, please): ")
val len = name.length
println(s"The name is $len character${if name.length != 1 then "s" else ""} long.")
end while
println(s"OK. Your name is $name. Hello!")
Between while
and do
, we write a Boolean
expression.
(This is similar to how we use if
and then
.) This condition
is evaluated before each execution of the loop body below.
The end marker at the bottom of the loop is optional. There’s also
no need to write a “go back to the top” command (such the one we
had in our pseudocode above). Whenever any while
loop reaches the
end of the loop body, it jumps back to check the looping condition.
The loop body is executed after the conditional expression has
been checked and evaluated to true
. This may happen multiple
times. In this example, the loop body executes each time name
is checked and holds an empty string. We indent the loop body
in the usual style.
The last line in our example isn’t part of the loop. It’s executed
only after the loop exits, which happens after the condition has
been checked and found to be false
.
Here’s a generic outline for a while
loop:
(Up here, you might have some commands that precede the loop and are executed just once.) while condition do One or more commands that are executed zero or more times, checking the condition before each iteration. If the condition is true, these commands are executed once more before rechecking. end while // optional line (After the condition evaluates to false the program advances to any commands that follow the loop.)
Another example of a while
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.
Practice reading while
loops
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 do
println("Letter: " + word(index))
index += 1
#1 Initialize: Set up the program’s initial state as
appropriate before starting the loop. What this often means in
practice is that you’ll need to initialize one or more var
s.
#2 Terminate: Make sure your loop is controlled by an appropriate conditional expression. In this example, we cap the value of the index variable; once we reach the limit, it’s time to stop.
#3 Advance the Loop: Your loop needs to modify the program’s state so that the loop will eventually terminate. Here, we increment the index variable so that it eventually reaches the limit we’ve set.
#4 Actually Do Stuff: Of course, you’ll need to include one or more commands that cause whichever effect your loop is designed to bring about. This toy loop’s only purpose is to print out some letters.
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, the computer keeps repeating the commands 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.2, 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
loop instead of LazyList
. The program’s
behavior should be the same as before. Write your code in task1.scala
in the
WhileLoops 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.
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 do
number += 1
doubled = number * 2
end while
doubled
end result
In the 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(s"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
while !isBig do
isBig = examine(Random.nextInt(100))
end while
In this solution, the find
call serves the same purpose
as the loop’s conditional: it governs the number of repetitions.
As generate random numbers in the Int
list, we'll call
examine
on each number. This yields a lazy-list of Boolean
s.
We do this until 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 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 works on and the operations 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 while
, you instead emphasize the step-by-step execution of commands.
This command brings 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, 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 while
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 probably fall in this category. The robot assignments below in this chapter could be argued to fit this description too.
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 a subprogram that requires optimization. Depending on circumstances, it might be beneficial to write a loop that painstakingly details the exact operations in the subprogram rather than leaving those minutiae to library methods.
Conditional loops such as 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 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 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.2 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”.
Lazy-lists are best suited for working on immutable data with
effect-free functions. When working on mutable state with
effectful functions, you need to be careful not to obscure
the step-by-step flow of the program too badly; in such
situations, a while
loop may be more readable than a
lazy-list.
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
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
mayMove
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) then 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 similar thing may have happened to you when you’ve called
attemptMove
.
Notice how the program’s design made it 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.
Wall
and Floor
are defined in the same file as their supertype Square
:
Square.scala
. Improve them as follows.
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: Slaybot
Implement Slaybot
.
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 a construct known as a
while
loop; 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.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.
for
loops and higher-order methods are often efficient enough and quite a bit more convenient thanwhile
loops.Links to the glossary: loop,
while
loop, iteration; lazy-list; level of abstraction.
Hey, What About break
and continue
? And return
? And Other Types of Loops?
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
rarely 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. (That is, the following code does essentially the
same as vector.find( _.nonEmpty )
, although this actually isn’t quite as efficient on
Vector
s as that library method.)
def firstNonEmpty(vector: Vector[String]): Option[String] =
var index = 0
while index < vector.size do
val element = vector(index)
if element.nonEmpty then
return Some(element) // Found it. Terminate the search without considering the remaining elements.
index += 1
end while
return None
The command terminates both the loop and the entire function.
It returns the value of the expression that follows the return
keyword.
The last line in this function’s body is only ever executed in
case the loop didn’t hit the early return
.
In Scala, methods with 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.2); they
make return
largely redundant. Higher-order methods and recursion are
particularly common in the functional programming style (Chapter 11.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.
(There are people who are stricter about this matter
and make a good case for it. The more functional
one’s programming style is, the more sense it makes to avoid return
.)
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
considering 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 — for
?
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:
// 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 many other tricks,
which you’ll learn about later.
More kinds of loops — do
?
Some programming languages have a kind of loop that begins with the
word do
and executes the loop body one or more times (cf. while
loops, which execute the body zero or more times). Old versions
of Scala used to have that, too, but it’s since been discarded as
superfluous.
If you’re insistent on writing a loop in modern Scala that executes the contents of the loop once before checking the looping condition for the first time, there’s this “trick” that you can do:
def printAtLeastOneSquare(limit: Int) =
var number = 1
var square = 1
while
println(square)
number += 1
square = number * number
square <= limit
do ()
That function prints out at least one line of output even if
limit
isn’t positive. Between the while
and do
keywords,
where the condition goes, we have a whole block of code
that begins with the loop’s actual contents and ends with
the conditional expression that controls the loop. On
each cycle through the loop, the three other lines get executed
first, and the condition on the fourth line of the block is
checked only afterward, so the entire block gets executed at
least once. After the do
keyword, where we’d normally have
a proper loop body, we now have just some empty brackets,
so nothing happens there.
Readers of such code may find it confusing, though. We do not recommend this style.
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 and so 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, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, Anna Valldeoriola Cardó, 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, Juha Sorva, and Jaakko Nakaza. 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; 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 has been designed and implemented by various students in collaboration with O1’s teachers.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
This is a
while
loop. We also use thedo
keyword that you already know fromfor
loops.