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 5.1: Logic, Blood, and Shopping
About This Page
Questions Answered: How do I write “If either that or that condition is met?” Or “If both conditions are met?” Can I write Scala classes independently already?
Topics: Logical operators are new. The programming assignments combine assorted earlier topics.
What Will I Do? Program, mostly. There’s a bit of reading at the beginning.
Rough Estimate of Workload:? Two or three hours? And quite a bit more if you do all the optional assignments, too.
Points Available: A120.
Related Modules: FlappyBug, Miscellaneous, AuctionHouse1 (new).
Intro: Let’s Make FlappyBug Harder
In Chapter 3.3, you wrote isLost
, a method that defines a Game
of FlappyBug to be
over if the obstacle touches the bug.
def isLost = this.obstacle.touches(this.bug)
As things stand, it’s too easy for the bug to advance simply by lurking at the top or
bottom. Let’s edit touches
so that the player loses if an obstacle hits or or the
bug is at the top or bottom of the playing area.
Here’s one way to do it:
def isLost =
if (this.obstacle.touches(this.bug))
true
else if (this.bug.pos.y <= 0)
true
else
this.pos.y >= GroundY
That works. But it’s a complicated way of expressing something rather simple. We’d much rather write: “If that condition or that other condition is met.” Perhaps you’ve already yearned after such an expression in the earlier chapters.
In this chapter, you’ll learn to combine Boolean
values with logical operators such
as “and” and “or”. We’ll get back to FlappyBug after a general introduction to these
operators.
Logical Operators
The Scala language supplies us with several logical operators (logiikkaoperaattori).
Operator | Name | Example | Meaning |
---|---|---|---|
&& |
and | someClaim && otherClaim |
“Are both Booleans true ?” |
|| |
or | someClaim || otherClaim |
“Is at least one of the Booleans true ?” |
^ |
exclusive or (xor) | someClaim ^ otherClaim |
“Is exactly one of the Booleans true ?” |
! |
not (negation) | !someClaim |
“Is the Boolean false ?” |
In O1, you’ll mostly find use for &&
, ||
, and !
. The last of those you already know
from earlier chapters.
The logical operators take in Boolean
values and also produce Boolean
s:
val number = 50number >= 0 && number <= 5res0: Boolean = false if (number >= 0 && number <= 5) "a valid grade" else "an invalid grade"res1: String = "an invalid grade" number == 100 || number == 50res2: Boolean = true !(number >= 0)res3: Boolean = false
&&
produces true
if and only if both
subexpressions are true
. Here, the first subexpression is
true
but the second isn’t, so the whole expression evaluates
to false
.||
produces true
if either of the two
subexpressions is true
(or both are). Here, the first
subexpression isn’t true
but the second is, so the whole
expression evaluates to true
.!
takes only
a single operand while the others take two.Non-strict evaluation of logical operators
The operators &&
and ||
are evaluated non-strictly (väljästi). What this means
for these operators is essentially that the left-hand operand is evaluated first and
if it alone determines the value of the logical operation, the right-hand operand
isn’t evaluated at all. (We will take a broader look at non-strict evaluation in
Chapter 7.1.)
This non-strictness has practical significance, as illustrated below.
First, let’s define a couple of variables. Here, we use literals, but you can easily imagine the numbers being read from the user’s keyboard, for instance.
val dividend = 50000dividend: Int = 50000 var divisor = 100divisor: Int = 100
Let’s divide one number with the other and see what we get. As we do so, let’s play
things safe and ensure that divisor
doesn’t equal zero. We can use either &&
or
||
to do that:
divisor != 0 && dividend / divisor < 10res4: Boolean = false divisor == 0 || dividend / divisor >= 10res5: Boolean = true
Assuming the divisor is not zero, we can swap the operands and still get the same result:
dividend / divisor >= 10 || divisor == 0res6: Boolean = true
But what if the divisor is zero?
divisor = 0divisor: Int = 0 dividend / divisor >= 10 || divisor == 0java.lang.ArithmeticException: / by zero ...
The computer performs the division as it evaluates the first operand. We get a runtime
error. We never even reached the expression divisor == 0
.
On the other hand, if we check the divisor in the first operand, things work out fine:
divisor == 0 || dividend / divisor >= 10res7: Boolean = true
Since divisor equalled zero, the expression divisor == 0
evaluated to true
. It
becomes unnecessary to evaluate the rest of the logical expression given that true ||
anythingAtAll
is always true
. Because of the non-strictness of these operators, the
computer doesn’t even evaluate the second operand. Execution order matters!
Combining logical operators
You can use rounds brackets to group logical expressions just like arithmetic and other expressions.
if ((number > 0 && number % 2 == 0) || number < 20) {
println("Either both positive and even, or less than 20.")
}
if (number > 0 && (number % 2 == 0 || number < 20)) {
println("Positive; moreover, either even or less than 20.")
}
Practice on logical operators
Study the following piece of code. Carefully consider what happens as it runs. You might wish to make notes on a piece of paper.
val first = 20
val second = 10
var third = 50
if (first > second && first + second < third) {
third = 30
println("One")
}
val result = first > second && first + second < third
if (first < second || result) {
println("Two")
}
if (first > second || second == third) {
println("Three")
}
third = 10
if (!(result && third >= 50)) {
println("Four")
} else {
println("Five")
}
Further improvements to the vending machine
Logical operators are a convenient replacement for multiple if
s.
Recall the sellBottle
method from Chapter 3.5. Here is the original Int
-returning
version:
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
}
}
Here are two attempts to reimplement the method with logical operators:
def sellBottle() = {
if (this.isSoldOut || !this.enoughMoneyInserted) {
-1
} else {
this.earnedCash = this.earnedCash + this.bottlePrice
// Etc. As above.
}
}
def sellBottle() = {
if (this.enoughMoneyInserted && !this.isSoldOut) {
this.earnedCash = this.earnedCash + this.bottlePrice
// Etc. As above.
} else {
-1
}
}
Assignment: FlappyBug (Part 15 of 17: Deadly Edges)
Make FlappyBug harder as suggested at the top of the chapter. Do it as follows.
- Add an effect-free, parameterless method
isInBounds
in classBug
. It should return aBoolean
that indicates whether the bug is within the legal playing area, that is, between the zero coordinate and the ground level (excluding those border values). Use a logical operator to implement the method. - Modify
isLost
in classGame
so that it also considers a game to be lost if the bug is out of bounds. Use logical operators and theisInBounds
method you just wrote.
A+ presents the exercise submission form here.
Assignment: Blood Types
Task description
The Miscellaneous module contains the package o1.blood
, which in turn contains the class
BloodType
. Implement that class so that it matches the module’s documentation.
Instructions and hints
This example shows how the class should work:
val myBlood = new BloodType("AB", true)myBlood: o1.blood.BloodType = AB+ val yourBlood = new BloodType("A", true)yourBlood: o1.blood.BloodType = A+ val theirBlood = new BloodType("O", false)theirBlood: o1.blood.BloodType = O- myBlood.canDonateTo(yourBlood)res8: Boolean = false yourBlood.canDonateTo(myBlood)res9: Boolean = true theirBlood.canDonateTo(yourBlood)res10: Boolean = true
You can also run the given
BloodTest
app to try out all the possible combinations of blood types.Once again, you don’t have to attend to special cases or erroneous input unless specifically instructed to. For example, you can count on any user of class
BloodType
to pass in only meaningful strings as constructor parameters.Try to implement the methods as simply as you can and without duplicating the same piece of code.
- Use logical operators. Can you make do
without
if
andmatch
altogether (at least outsidetoString
)? - Call the other methods of the same class.
Further tip: you can pass
this
as a parameter to a method.
- Use logical operators. Can you make do
without
A+ presents the exercise submission form here.
Assignment: FixedPriceSale
The Scaladocs in module AuctionHouse1 describe several classes for representing items that have been put up for sale in an imaginary online auction house. In this assignment, we’ll focus on a single way to sell an item: simply giving it a fixed price. Later assignments will features auctions and changing prices.
Task description
Fetch the AuctionHouse1 module and view its Scaladocs. Implement class FixedPriceSale
of package o1.auctionhouse
so that its behavior matches the documentation.
Instructions and hints
Start by creating a new file for the class. Here’s a recap:
- In the Project tab, right-click
the package
o1.auctionhouse
. Select New → Scala Class. - Enter a name for the class:
FixedPriceSale
. - A new Scala file should appear with the
appropriate
package
definition at the top.
- In the Project tab, right-click
the package
You may wish to use an instance variable of type
Option
, with an initial value ofNone
. If you do, remember that you need to annotate the variable with a data type (as in Chapter 4.3). Like this:nameOfVariable: Option[Type]
.Additional hints about instance variables
You should make two instance variables of the three constructor parameters. You’ll find these two public variables listed in the docs.
You’ll need a couple of more variables: one to track the number of remaining days and another to store the buyer’s name.
Since there isn’t necessarily any buyer,
Option[String]
is a suitable type for the latter variable.The two variables just mentioned aren’t listed in the docs; they are part of the class’s internal implementation. Make them private and name them as you please.
The Scaladocs list the class’s members in alphabetical order, but that’s probably not the most convenient order for you to implement them (nor do the methods need to be in that order in your code). You are free to choose the order in which you implement the methods, but here’s one suggestion:
toString
,daysLeft
,buyer
,isExpired
,isOpen
,advanceOneDay
,buy
.Hints about the methods
Study the docs with care. Notice, for instance, that “open” and “expired” aren’t exact opposites even though an item cannot be both at the same time.
Most of the methods are effect-free. They only need to return the value of an instance variable or check whether the instance variables meet a certain condition. A single-line expression will do as the implementation for each of these methods.
Items have two effectful methods:
advanceOneDay
changes the number of days remaining andbuy
changes who’s bought the item. You should have each of those things available in instance variables. Remember, in both methods, to check whether the sale was open; you can callisOpen
to do that.You can use the app object
FixedPriceSaleTest
for testing. Use it.- The app object initially generates error messages, which is simply because you haven’t yet implemented the methods that the app calls. The errors will go away once you do the assignment. (Remember: the cause of an error is not always where the message points to.)
- You can initially “comment out” part of the test app if you want to test a partial version of your class.
You can also run
TestApp
, a program ino1.auctionhouse.gui
that lets you create and manipulateFixedPriceSale
objects in a graphical window that looks like this:
A+ presents the exercise submission form here.
Optional Practice: Moment
and Interval
The rest of the chapter consists of practice tasks that are voluntary but highly recommended. Take them as an opportunity to build up fluency. If the weekly deadline is ominously close, you might wish to do the other chapters first before returning here. The assignments below will probably take several hours to complete.
Introduction to o1.time
This problem will let you apply the logical operators more extensively and freely than the preceding ones. It also provides practice on a number of other concepts.
Moments and intervals
Imagine we have an application (or several) that needs to represent time intervals:
an interval begins at a particular moment of time and continues until another moment.
We intend to model these concepts as the classes Moment
and Interval
. Each
Interval
object is associated with two Moment
objects that represent the interval’s
first and last moment.
The Miscellaneous module contains a package o1.time
and, therein, a partial
implementation for class Moment
. Interval
is missing entirely.
The given code for Moment
is explained below.
import scala.math.abs
class Moment(private val time: Int) {
override def toString = this.time.toString
def distance(another: Moment) = abs(another.time - this.time)
def isLaterThan(another: Moment) = this.time > another.time
def later(another: Moment) = if (this.isLaterThan(another)) this else another
def earlier(another: Moment) = if (this.isLaterThan(another)) another else this
}
Moment
instance, we specify the moment in time
that it stands for. Our implementation doesn’t commit to any
particular unit of time (days, years, nanoseconds, etc.); any
integer will do.Moment
delegates its whole toString
method to the integer
it stores. Notice that you can call a method on an Int
! An
Int
’s toString
method returns the characters that correspond
to the number; e.g., for the integer 123 we get the string "123"
.
(We will discuss the methods on Int
s further in the next
chapter, 5.2.)distance
method for determining how far apart two
moments are.isLaterThan
, later
, and earlier
compare moments in
different ways.General hint: make use of abstractions
One of this problem’s main themes is to learn to make use of the other methods in the classes that you write. Sure, you’ve already done that in ealier assignments, too, but this one particularly highlights the matter.
If you write the required methods completely independently of each other, you’ll find yourself duplicating code. Don’t do that. Consider what other methods you’ve created that you could call. Try to keep your code DRY. Non-redundant code is easier to modify and less vulnerable to bugs.
In fancier words: Every method is an abstraction (Chapter 1.6). You should build new
abstractions atop existing ones. For example, the method isLaterThan
is as an
abstraction of its concrete implementation, which takes the values of two instance
variables and uses an operator to compare the two: this.time > another.time
. The other
methods that need to compare two Moment
s can simply use this method and don’t need to
deal with the variables directly.
A task in four phases
Implement Interval
and Moment
so that they meet the specification. Below is a
recommended workflow that you can either follow or ignore.
Phase 1 of 4: Getting started with Interval
- Read the introductions of both classes in the Scaladocs. Also
read the docs for
Interval
’s methodslength
andtoString
, which you’ll implement first. - Create a new file for class
Interval
. - Define the constructor parameters. Since each parameter needs to be stored in an instance variable, define those, too, in the class header. Should these instance variables be private?
- Implement
length
. Use a method from classMoment
.- Now and later: resist the temptation to
add public members in class
Moment
beyond those request in the docs. In particular, don’t make the instance variabletime
public.
- Now and later: resist the temptation to
add public members in class
- Implement
toString
. It’s a bit more complicated. Here are some hints:- Use the methods you have at your disposal.
- You can use an
if
–else
structure to cover the three different cases. - Remember that a string can be “multiplied”.
For instance,
"ho" * 3
yields"hohoho"
.
- See the starter code in
TimeTest
. Add commands there as you see fit: createInterval
objects and call their methods.
When are two Moment
s equal?
If you use ==
or !=
to compare moments , what you get is an identity comparison
(“Do these two references point to the same Moment
object?”; see Chapter 3.3.)
What you don’t get is a comparison of the Moment
objects’ actual contents. These
operators won’t tell you whether two distinct Moment
objects represent the same
moment in time, that is, whether their time
variables have identical values.
In this assignment, identity comparison and those two operators are completely unnecessary. You don’t stand to gain anything from using them. Other methods will serve you better!
Phase 2 of 4: isLaterThan
and overloading
The interval assignment was quite a tranformative experience.
I was already well on my way, writing an if
statement in
each method and not reusing methods, when a couple of formidable
and eagle-eyed course assistants, who just happened to be in the
room, pointed this out to me.
I didn’t dare defy the wise assistants so I racked my brain,
sweating, to figure out how it could even be possible to manage
without an if
. Once I came up with a solution for isLaterThan(Interval)
(helped by a hefty dose of hints from the assistant), I felt like
I had just invented the wheel. It was ingenious!
- Add
isLaterThan
in classInterval
. Note that the method name has been overloaded (Chapter 4.1): there are two methods with the same name. One takes in aMoment
and the other anInterval
. Implement the former first. Make use of an existing method. - Implement the second
isLaterThan
method inInterval
. Make use of the method you implemented just before. As you do so, remember a detail from Chapter 4.1: if an overloaded method calls its namesake, Scala requires an explicit type annotation for the calling method’s return type. That means you’ll need to annotate the type on at least one of theisLaterThan
methods. - Add more test code to
TimeTest
and run it. Confirm that bothisLaterThan
methods work as intended. Moment
should also have anisLaterThan
method that takes in anInterval
parameter. Add that method to the class.
Phase 3 of 4: logical operations
Continue implementing methods on Interval
and Moment
:
- Of the two
contains
methods ofInterval
, implement the one that takes in aMoment
. Use logical operators in combination with methods that you have previously implemented. - Implement the other
contains
method. Again, use logical operators and the other methods. Also recall what was just said about overloaded methods. - Implement
isIn
in classMoment
. It essentially does “the same thing in reverse” as another method you already wrote, which gives you a very simple implementation for it. - Implement
overlaps
in classInterval
. Once again, you can find uses for logical operators and existing methods. Make sure you cover all the possible cases. - Add more method calls in
TimeTest
and test your new methods.
Phase 4 of 4: combining instances to produce new ones
The methods union
and intersection
are still missing from class
Interval
. Implement them and you’re done.
A+ presents the exercise submission form here.
How private is private
?
Student question: How can that Moment
class work? time
is private!
Let’s take a moment to consider why the given code from the previous assignment doesn’t produce an error message.
class Moment(private val time: Int) {
def isLaterThan(another: Moment) = this.time > another.time
time
is private
. It’s not available for external use.
But what counts as external, exactly?Moment
object can access its own time
.Moment
object can also access the time
of another
Moment
object: we simply prefix the variable name with
an expression that refers to another object of the same
class. Here, that expression is the variable name another
.private
indicates that a program component is for internal use within the
same class. The private component isn’t accessible by only a single object
but also by the class’s other instances.
In the words of the late Nobelist:
All human beings have three lives:public, private, and secret.—Gabriel García Márquez
In Scala, “secret” is written private[this]
. Place that modifier in
front of a variable definition, and the variable will be accessible by
only an individual object. In O1, we won’t be using such “secret”
variables.
Omitting val
/var
and private[this]
This optional section deals with a minor Scala-specific issue. Read or skip as you please.
We’ll find an answer to this good question from a student:
The answer isn’t as simple as one might hope. It involves a curious little feature of Scala, which we can explore through this simple example class:
class MyClass(val first: Int, second: Int) {
val third = second * second
def myMethod = this.first
}
In other words, the student’s question is this:
first
wasn’t preceded by val
or var
but...first
?second
, which has not been
marked as an instance variable?The student suggested that this might crash the program at runtime. It might be even better if such an attempt produced a compile-time error before the program can be run. Neither of those things is what actually happens according to Scala’s rules, though.
Before seeing what actually happens, let’s recap these facts about constructor parameters and instance variables in Scala:
- If you write just
val
/var
in front of a constructor parameter, that parameter’s value will be assigned to an instance variable. Unless you specify otherwise, the instance variable will be public. - If you add
private
, the instance variable won’t be accessible to the outside but the class’s own code can access that variable, no matter which of the class’s instances is involved. - If you write
private[this]
instead, the instance variable will be an individual instance’s “secret”. The class’s methods can only access that variable on thethis
object. - If you don’t prefix a constructor parameter
with anything (no
val
,var
,private
, etc.), then that parameter is meant to be used only in the code that initializes the instance (the constructor). Unless you writeval
orvar
in front, you won’t get an instance variable; that’s the basic idea, anyway. But see below.
class MyClass( first: Int, second: Int) {
val third = second * second
def myMethod = this.first
}
first
isn’t marked as
a val
. Normally, a constructor parameter
thus defined would be available only while
creating instances. Its value wouldn’t be
stored in an instance variable as part of
the object and wouldn’t be accessible to
the class’s methods. Except that...val
, non-var
constructor parameter
in a method, Scala implicitly defines a
“secret” instance variable on your class.The above code works exactly as if you had defined the parameter
as private[this] val first: Int
. In O1, we won’t be writing code
like that.
Optional Practice: Auctions
Assignment: DutchAuction
Go back to AuctionHouse1. Implement class DutchAuction
as specified.
Instructions and hints:
- First and foremost: make sure you understand what “Dutch auctions” are! The details are in the Scaladocs. Unless you have a clear idea of what the class should do, implementing it will be tough going.
- As you read the docs, you’ll notice that some parts of
DutchAuction
are identical toFixedPriceSale
. You can copy parts of your earlier solution into yourDutchAuction
class. - You can again use
TestApp
fromo1.auctionhouse.gui
to experiment with your class. You can of course also write your own text-based program for testing. - If you have an
Int
and need aDouble
, you can either
A+ presents the exercise submission form here.
Are you a bit upset that you had to write the same methods again in class
DutchAuction
or at least copy them from one file to the other? Outstanding!
That means that you’re motivated to reach Chapters 7.2 and 7.3 where you’ll
learn about avoiding such code duplication.
Assignment: EnglishAuction
Stay with AuctionHouse1 and implement EnglishAuction
.
Instructions and hints:
- Again, first make sure you understand exactly how “English auctions” are expected to work.
- A simple
Bid
has been provided, which you should find useful. There is also a bit of starter code forEnglishAuction
. - Even though it’s possible to solve the assignment with a buffer that stores all bids placed on an item, you don’t actually need to do that. As it turns out, you don’t need to solve every last bid to fullfill the requirements...
- Instead, you can use two variables to store just the two highest
bids, as shown in the starter code. Notice these two variables’
initial values: when there are no actual bids yet, the variables
refer to “empty”
Bid
s equal to the starting price. TestApp
will again be of service.
A+ presents the exercise submission form here.
Summary of Key Points
- Logical operators operate on Boolean values. You can use them to express meanings such as “X and Y” and “X or Y”.
- Links to the glossary: logical operator; to overload; DRY.
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.
touches
returnstrue
orfalse
. Consequently,isLost
also return one of the twoBoolean
s.