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. Myös suomenkielisessä materiaalissa käytetään ohjelmaprojektien koodissa englanninkielisiä nimiä kurssin alkupään johdantoesimerkkejä lukuunottamatta.
Voit vaihtaa kieltä A+:n valikon yläreunassa olevasta painikkeesta. Tai tästä: Vaihda suomeksi.
Chapter 3.4: Soda, Football, and Errors
About This Page
Questions Answered: How are Scaladocs used in O1’s programming assignments? How can I fix some common errors in my Scala code? When will I learn to understand the error messages that Eclipse churns out?
Topics: Additional practice on class definitions, the if
command,
and other constructs. Error messages and hunting for errors. Scaladocs
as specifications.
What Will I Do? Read programs and program. You’ll need to marshal multiple concepts and constructs to produce code that works.
Rough Estimate of Workload:? Two or three hours.
Points Available: A105.
Related Projects: Miscellaneous (new), Football1 (new).
Example: VendingMachine
Our next example involves virtual vending machines. We’ll create a class that purports to control a simple machine that sells bottles of soda. (Disclaimer: this is another intro-course example that cuts many corners.)
We’ll implement the class, VendingMachine
, piece by piece, from a specification given as
a Scaladoc document (Chapter 3.1). This class will be somewhat more
complex than the ones we’ve examined so far and combines multiple themes from earlier chapters.
The example doesn’t feature any new programming concepts or Scala constructs, so if you feel
that you have a firm grasp on what we have covered so far, and if you aren’t wrong, you could
gloss over this example and skip ahead to the assignments that follow. (Or you may wish to
take up the additional challenge of implementing class VendingMachine
on your own before
studying the solution below!)
Read the documentation NOW
Study the Scaladocs
of o1.soda.VendingMachine
in project Miscellaneous.
Before reading further in this section, make sure you understand the methods’ descriptions in the documentation.
The project Miscellaneous contains a working implementation for
VendingMachine
, so you can already try using the class in the
REPL if you feel something was left unclear by the docs. We’ll
go through the implementation in detail below.
Initializing the state of a VendingMachine
Consider, first, the constructor parameters and instance variables that we need for implementing the class.
class VendingMachine(var bottlePrice: Int, private var bottleCount: Int) {
private var earnedCash = 0
private var insertedCash = 0
var
will serve us as a gatherer
(Chapter 2.6). This instance variable is meant only for
the class’s internal and shall be private (Chapter 3.1).
Note that you can also enter the private
modifier where you
define an instance variable in the class header.earnedCash
records the amount of euro cents the
machine has earned by selling bottles since it was last emptied.
We need to keep a tally of these earnings in order to the
emptyCashbox
method to work as specified. The variable is for
internal use only, so private
. A newly created machine object
hasn’t yet earned a cent.insertedCash
records the number of euro cents a
customer has inserted since the previous purchase. For example,
if the customer has inserted first 2 €, then 1 €, and no
bottle has been purchased since, the variable should have the
value 300. We need this variable for sellBottle
to work as
specified. It, too, is private and initially zero.A few simple methods
Five of the methods are straightforward to implement given what you know from earlier chapters.
def addBottles(newBottles: Int) = {
this.bottleCount = this.bottleCount + newBottles
}
def insertMoney(amount: Int) = {
this.insertedCash = this.insertedCash + amount
}
def isSoldOut = this.bottleCount == 0
def enoughMoneyInserted = this.insertedCash >= this.bottlePrice
def emptyCashbox() = {
val got = this.earnedCash
this.earnedCash = 0
got
}
addBottles
and insertMoney
simply use their parameters to
increase the values of the corresponding gatherer variables.isSoldOut
and enoughMoneyInserted
each use a comparison
operator to check for a particular situation and return the
result as a Boolean
.emptyCashbox
resets the the machine’s earnings to zero and
returns the earnings that had accumulated before the reset. The
method uses a local temporary variable much like the account
object did in Chapter 2.2.Perhaps it’s also worth our while to take another look at the same code while bearing in mind Scala’s punctuation rules and style conventions:
def addBottles(newBottles: Int) = {
this.bottleCount = this.bottleCount + newBottles
}
def insertMoney(amount: Int) = {
this.insertedCash = this.insertedCash + amount
}
def isSoldOut = this.bottleCount == 0
def enoughMoneyInserted = this.insertedCash >= this.bottlePrice
def emptyCashbox() = {
val got = this.earnedCash
this.earnedCash = 0
got
}
Implementing toString
The toString
method employs an if
expression:
override def toString = {
"earned " + this.earnedCash / 100.0 + " euros, " +
"inserted " + this.insertedCash + " cents, " +
(if (this.isSoldOut) "SOLD OUT" else this.bottleCount + " bottles left")
}
if
expression as
a subexpression within the method body.if
expression are needed for
correctly delimiting the else
branch (cf. the practice tasks
near the beginning of Chapter 3.3).Implementing sellBottle
One way of implementing sellBottle
is shown below. This method should do the necessary
bookkeeping on money and bottles when a bottle is bought and return either the amount of
change given to the customer or a negative number to signal an unsuccessful purchase.
def sellBottle() = {
if (this.isSoldOut) {
-1
} else if (!this.enoughMoneyInserted) {
-1
} else {
this.earnedCash = this.earnedCash + this.bottlePrice
this.bottleCount = this.bottleCount - 1
val changeGiven = this.insertedCash - this.bottlePrice
this.insertedCash = 0
changeGiven
}
}
isSoldOut
to confim there is enough product. In case
we’re out of soda, we return -1.else if
combination.Boolean
value around: here, we check to see if there is not
enough money. If so, we return -1.Is our implementation good?
The above implementation of sellBottle
meets the specification. But you might agree
that its sequence of if
s looks a bit unwieldy. The code is also not completely DRY
(i.e., free of redundancy) as the same command — returning -1 — unnecessarily features
twice.
We should also question whether it’s a good idea to communicate an unsuccessful purchase by returning a negative number — even though signaling failure with a negative number is a classic programmer’s trick. By doing so, we invite an error from any user of the method: it’s easy to forget to check the sign of the returned number and instead handle the minus one as if it was the amount of change received for a successful purchase.
Soon enough, you’ll learn other techniques that we could improve sellBottle
. You
may wish to think back to this method during Week 4.
Scaladocs as Specifications in O1’s Programming Assignments
Scaladoc documentation will play an increasingly important role in O1, as many upcoming programming assignments use Scaladocs in two ways:
- In many assignments, you need to use one or more existing classes that have been custom-made for O1 and documented as Scaladocs. You’ll receive both the executable code and a description of its interface: its “user manual”.
- In many assignments, you’ll receive Scaladocs that describe
the interface of one or more classes whose implementation you
don’t have (at least not all of it). Your task will be to write
Scala code to match the given documentation. In other words, the
Scaladocs serve as a specification for your program, much as
with
VendingMachine
just now.
Private variables in Scaladoc-based assignments
Recall from Chapter 3.1: private variables aren’t part of a class’s public interface and therefore aren’t included in Scaladocs that describe how a programmer can use the implemented class.
What that means for O1’s Scaladoc-based programming assignments is: your classes should
have all the instance variables listed in the Scaladocs and those variables should be
public (like bottlePrice
above). On the other hand, your class shouldn’t have public
instance variables that aren’t listed in the specification.
It’s not unusual that you’ll need additional instance variables for your class’s
internal use. Make those additional variables private (like bottleCount
and
earnedCash
in our example).
Check your understanding
So, it’s possible to access private
data?
(You don’t need to know this in O1. Or often outside of O1, either, for that matter.)
The private
modifier prevents a variable (or method) from being
used outside of the class in the usual fashion. It marks the fact
that, under normal circumstances, it doesn’t make sense to access
that variable from the outside. Even so, as hinted in the feedback
to the above question, private
isn’t an unsurmountable obstacle.
Consider class VendingMachine
. One of its private methods is
earnedCash
, whose value we adjust by calling public methods
on a VendingMachine
object. The following attempt to access it
fails, as it should:
val machine = new VendingMachine(250, 10)machine: o1.soda.VendingMachine = earned 0.0 euros, inserted 0 cents, 10 bottles left machine.earnedCash = 123456<console>:9: error: variable earnedCash in class VendingMachine cannot be accessed in o1.soda.VendingMachine machine.earnedCash = 123456 ^
But this works:
val accessToCurrentValue = machine.getClass.getDeclaredField("earnedCash")accessToCurrentValue: java.lang.reflect.Field = private int o1.soda.VendingMachine.earnedCash accessToCurrentValue.setAccessible(true)accessToCurrentValue.set(machine, 123456)machine.emptyCashbox()res0: Int = 123456
This little trick bypassed the class’s intended interface and enabled
us to assign an arbitrary value to earnedCash
. But this isn’t
something that you’ll do by accident.
Assignment: Spot the Errors
Open the project Football1. This program, which records the results of football (soccer)
matches, contains two classes named Match
and Club
as well as the app object MatchTest
.
Class Match
uses class Club
: each football match features two competing clubs.
MatchTest
is a simple application for testing the behavior of the two classes.
Task description
Read the Scaladocs that come with the project and browse the program code. (Reminder: you
can find an O1 project’s Scaladocs by opening the doc
folder either within Eclipse or
through the “Related Projects” links at the top of each chapter.)
Examining the code, you’ll eventually notice that:
- It contains a number of syntax errors (“grammatical errors”; Chapter 1.8) that prevent the classes from being used.
- Some of the methods listed in the documentation are missing. And that’s still not all, because:
- There are bugs in functionality: some of the methods in class
Match
are syntactically valid but don’t work as specified in the Scaladocs.
Your task is to:
- fix the errors in classes
Match
andClub
and fill in the missing methods so that the classes conform to the specification; - adjust the test program
MatchTest
so that it works, that is, so that it correctly uses classMatch
and its behavior matches the comments embedded in its program code; - expand on the given test program so that it tests additional features of the classes (as you see fit); and
- use the test program to convince yourself that each method works as intended.
Instructions and hints
The errors in the given code resemble actual errors made by beginner programmers before you. Learning to find and address these mistakes can help you avoid them in future assignments where you program more freely.
Without guidance, this assignment is quite hard for a beginner. We strongly suggest that you follow the guided ten-phase approach below.
You did make sure to read the Scaladocs before you begin, right? And at least skimmed the given code to get your bearings?
Phase 1 of 11: Club
Take a look at the Problems tab in Eclipse. Initially, all the problems
listed there mention class Club
. Don’t be fooled by this; there are errors in other
parts of the program, too. It’s just that, as it happens, the error in class Club
is
such that Eclipse doesn’t even list the other problems before you fix Club
. It’s not
rare that one error in a program conceals other errors behind it.
Let’s begin with class Club
. According to Eclipse, there are as many as three
errors in this class, some on the header row and some at the very end of the file. Don’t
take this too literally. When a tool such as Eclipse attempts to parse a program,
and something goes wrong (because the program is invalid), it often happens that one
mistake causes the entire parsing process to fail. A single parsing error can make the
computer see the entire program as invalid in numerous ways. Invalid brackets and other
punctuation errors, for instance, may entirely throw off the automatic parser.
There is actually just a single, small error in class Club
, albeit one that occurs
twice. Eclipse’s error message ':' expected but ',' found hints at the problem. It can be
paraphrased as: “There was supposed to be a colon here, but there was a comma instead.”
There’s a red highlight at the comma between the constructor parameters in val name,
val stadium
.
Why should there be a colon there? What else is missing from that line? You can spot the problem yourself. Or if not, compare the given class definition to the example classes in this ebook.
Some error messages are rather more opaque than that one. It may take quite a bit of brainwork and googling to work out what a message means. Practice makes perfect.
Phase 2 of 11: isHigherScoringThan
Save the file. The list of error messages updates. Now we have a flood of red in both Match
and the app object MatchTest
.
Let’s deal with Match
first. Click the error message in the Problems tab
to locate the corresponding line of code.
According to Eclipse, the defective bit is this.totalGoals(anotherMatch)
. And
indeed there is something wrong with this method call. Can you tell what it is? Fix this
error and another similar one within the same method.
But what’s with that error message? Eclipse’s complaint about the method call was: Int does not take parameters. At first glance, the message may seem weirder than the error itself, but it does make some sense if you know how to interpret it.
We know that totalGoals
returns an integer. The method takes no parameters;
this.totalGoals
is an expression of type Int
. That being so, the Scala toolkit
interprets this.totalGoals(anotherMatch)
as an attempt to “invoke an Int
with
anotherMatch
as a parameter”, which makes no sense; hence the above message.
After fixing this — and remember to save your work again — class Match
seems
to be in good shape. Hold that sigh of relief, though.
Phase 3 of 11: MatchTest
and creating match objects
One of the remaining complaints concerns the command new Match(club2, club1)
in the
MatchTest
object. What’s wrong with that? It looks fine. It is fine.
This is a good example of how an error message doesn’t always point to the spot where
the actual error is. What we have here is a message saying that MatchTest
doesn’t work,
and indeed it doesn’t, but the reason is that Match
has been incorrectly defined,
whereas MatchTest
attempts to use it like it’s supposed to be used.
On error messages in Scala
Unfortunately, not all of the error messages produced by current Scala tools are terribly informative. One positive about them, though, is that due to Scala’s type system (Chapter 1.8), many of the programmer’s blunders will manifest themselves as type mismatch errors and similar type-related messages that are relatively easy to address (assuming that some of the more complicated features of Scala’s type system aren’t involved).
Type mismatch is a common error message that, in simple cases, points us directly to
the problem. Here, too, the message is pretty clear as to what caused it. Read the
message, then compare the indicated line, the Scaladocs of class Match
, and the given
implementation of Match
. Fix the error.
Phase 4 of 11: addAwayGoal
Now focus on the error messages that say: value addAwayGoal is not a member of
o1.football1.Match. That is: there is no addAwayGoal
method in class Match
.
Eh? The method is defined right there; look for yourself.
Did you look carefully?
When you get a message like this, the first thing to do is check your spelling. There may be a typo in the definition or in the code that uses the definition.
Phase 5 of 11: homeCount
and awayCount
Next up: variable awayCount in class Match cannot be accessed and the same story for
homeCount
.
These error messages are fairly apt. They tell us that those variables in Match
cannot be accessed from class MatchTest
.
If you didn’t already notice the variables homeCount
and awayCount
in class Match
,
and the methods homeGoals
and awayGoals
, notice them now.
What is the problem? Is the error in Match
or MatchTest
?
Fix the problem.
Phase 6 of 11: totalGoals
A couple of the error messages involve the method call match1.totalGoals()
. The
complaint is familiar: Int does not take parameters. The reason is familiar, too, even
though here we don’t attempt to pass any actual parameters, just an empty parameter list.
Fix the problem.
Phase 7 of 11: Creating Match
objects, again
A line of code in MatchTest
brings another protest from Eclipse: not found: value
Match. This means, roughly: “I can’t evaluate the expression Match
.”
The message doesn’t do a great job of explaining what’s wrong with that line. But it does
supply the clue that the Scala toolkit has attempted to interpret the individual word
Match
as the name of a variable or a method. Which isn’t what we want; we want to create
a new Match
object. The fix is very simple.
Phase 8 of 11: isGoalless
Another error: type mismatch; found: AnyVal, required: Boolean. The message points to
the isGoalless
calls in MatchTest
.
It’s clear that we need Boolean
expressions for the if
s in MatchTest
. But somehow,
isGoalless
appears to produce not a Boolean
but something called AnyVal
.
Can you figure out or guess what’s wrong in isGoalless
? Can you fix the problem and
simplify the method at the same time? If not, take a look at the section More Errors:
Return Values and Selection below; it discusses this specific sort of error.
Phase 9 of 11: location
The remaining compile-time errors from MatchTest
also arise from defects in Match
.
The message value location is not a member of o1.football1.Match tells us that we’re
missing a location
method or variable in Match
. The symptom is similar to the
addAwayGoal
case above, but this time the cause is different: this method is actually
missing.
Implement location
.
Phase 10 of 11: Testing
The program should be ready to run. Launch MatchTest
. Consider what it should print
out and see what it actually prints out.
You’ll notice that some of the output is fine, but all is not well. Some of the methods
in Match
still don’t meet the specification even though Eclipse can’t spot any more
errors for us.
See below for hints.
Phase 11 of 11: Fixes and toString
Fix the flaws that remain in class Match
. Also write the missing method toString
.
Instructions and hints:
- The empty brackets
()
in the printout are the result of aUnit
return value being printed (cf. Chapter 1.6). - As you search for the remaining logical errors, edit
MatchTest
as you see fit. - A reminder (for the last time): you should click the method names in the Scaladocs to see the full descriptions.
- If you have trouble implementing
toString
, try the following.- Make sure you read its entire Scaladoc.
- Make sure you included an
override
prefix. - Take a look at the remainder of this
chapter. You may have run into one of the
if
-related errors discussed below. - Ask for help if you’re stuck.
Submission form
A+ presents the exercise submission form here.
Words of Warning
Many programming tools, Eclipse included, occasionally notify the programmer about questionable code with a warning (varoitus). In Eclipse, warnings look like compile-time errors, except that they are yellow rather than red. It may be that you haven’t yet come across such a warning message but sooner or later you will.
A warning means that the code probably (but not certainly) has a problem worth addressing. By issuing a warning, Eclipse tells you the programmer: “Are you sure you really want to be doing that?” When Eclipse warns you about something, it’s very often right in that something in the program should be changed. The automatic message may be less insightful as to what should be changed, though.
A good rule of thumb is to take your warning messages like you take your error messages: seriously.
More Errors: Variables and Selection
Let’s look at some more code that doesn’t work. The remaining examples in this chapter
can improve your understanding of if
expressions and other Scala constructs. They may
also help you avoid some common mistakes.
Below is a function that resembles the library function min
. This function was written
by an earlier O1 student who was puzzled by the error message that it produces. Many beginners
have made a similar mistake.
def returnSmaller(first: Int, second: Int) = {
if (first < second) {
var result = first
} else {
var result = second
}
result
}
result
is undefined
on the last line of the function body.result
is defined? Twice no less.The problem is this: result
is defined only within the branches of the selection
command. Each of these two definitions covers only that branch, and neither of the
two variables can be used outside of the if
.
If we want to access the variable after the selection command, we need to define it outside the command. This works, for example:
def returnSmaller(first: Int, second: Int) = {
val result = if (first < second) first else second
result
}
This example has brought us in touch with the fact that each variable has a scope in which it is available (käyttöalue). That’s something we’ll discuss further in Chapter 5.4.
Just to be clear: in the previous example, it isn’t necessary to store the result in a variable. The simpler implementation below also works.
def smaller(first: Int, second: Int) = if (first < second) first else second
More Errors: Return Values and Selection
Why doesn’t this work?
Suppose that we wish to write a function that takes an Int
and returns an Int
. In case
the given number is positive, the function returns the square of that number. In case the given
number is negative, the function returns zero instead.
Here’s an attempted solution:
def experiment(number: Int) = {
if (number > 0) number * number
if (number <= 0) 0
}
This code explicitly articulates each of the two cases with a separate if
. However, it
also contains a common beginner’s mistake. Let’s try it in the REPL:
def experiment(number: Int) = { if (number > 0) number * number if (number <= 0) 0 }experiment: (number: Int)AnyVal
The REPL accepts the function definition; this is a valid Scala function. But we run into trouble as soon as we use the returned number in a computation:
7 + experiment(10)<console>:12: error: overloaded method value + ... cannot be applied to (AnyVal) 7 + experiment(10) ^
The error message means, roughly, “the plus operator isn’t defined for the data type
AnyVal
”. What is AnyVal
?
Why it doesn’t work
The error message is a bit odd but it does have a point.
Let’s take a good look at our code and bear in mind that the function’s return value is the value of the expression that is evaluated last.
def experiment(number: Int) = {
if (number > 0) number * number
if (number <= 0) 0
}
if
expression whose value is the
square of the given number in case the number was positive. Otherwise
this first if
expression “has no value”; that is, it has only the
contentless value Unit
.if
expression, which similarly yields
either an Int
value (of zero) or Unit
.if
was completely inconsequential.Side note
Our else
-less expression if (number <= 0) 0
is effectively a
shorthand for the expression if (number <= 0) 0 else ()
in which
the empty brackets stand for Unit
.
Since this (poorly defined) function returns either an Int
or Unit
depending on
circumstances, the Scala toolkit infers its return type to be AnyVal
, which you can
think of as meaning “some kind of value”. Since addition has been defined for two numbers,
not for a number and “some kind of value”, our attempt to evaluate 7 + experiment(10)
fails.
A version that works
def experiment(number: Int) =
if (number > 0) number * number else 0
if
expression whose value is of type Int
no matter
which branch is chosen.We’ll have more to say about AnyVal
in due course (Chapter 7.3).
How about another solution?
A couple of tips for error-hunting
When you get a puzzling error message, check the data types of the expressions and return
values in your program. This advice is generally sound, and it’s especially pertinent
when the message contains the word “Any” and you have used an if
.
The REPL displays the type of each expression that you enter. And in Eclipse’s code editor,
you can hover the mouse cursor above the code to view type information. In the screenshot
below, the cursor hovers above the word experiment
, displaying the function’s parameters
and return type:
Another trick: in some cases, you’ll get more informative error messages if you annotate your function with an explicit return type (Chapter 1.8), as shown below:
def experiment(number: Int): Int = // etc.
Feel free to annotate very function’s return type like this, if you want. These voluntary annotations can clarify program code and reduce the likelihood of future errors. Many Scala programmers add a type annotation to every public method (which is however not required of you in O1).
Summary of Key Points
- In O1, many programming assignments come in the form of Scaladoc documents.
- The error messages that programming tools produce may be cryptic, but with practice, you’ll learn to interpret them.
- When you know that the branches of an
if
cover all the possible cases, make this clear to your programming toolkit with a finalelse
branch that has no conditional. - Links to the glossary: documentation, Scaladoc;
if
; compile-time error, syntax error.
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!
Weeks 1 to 13 of the ebook, including the assignments and weekly bulletins, have been written in Finnish and translated into English by Juha Sorva.
Weeks 14 to 20 are by Otto Seppälä. That part of the ebook isn’t available during the fall term, but we’ll publish it when it’s time.
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 programmed by Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, 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 have done 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 tools from O1Library (such as Pic
) for simple graphical programming
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+ has been created by Aalto’s LeTech research group and is largely developed by students. The current lead developer is Jaakko Kantojärvi; many other students of computer science and information networks are also active on the project.
For O1’s current teaching staff, please see Chapter 1.1.
Additional credits appear at the ends of some chapters.
bottlePrice
, has the role of most-recent holder (Chapter 2.6). Since we let the user assign new values for the price, we use avar
(which is also specified in the documentation).