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 4.4: Exercises in Not Existing
About This Page
Questions Answered: Could I practice writing classes some more,
please? How can I make use of the Option
type? And match
?
Topics: The constructs from the previous chapter.
What Will I Do? The first half of the chapter is a series of small programming assignments. The second half contains some voluntary reading.
Rough Estimate of Workload:? Three or four hours.
Points Available: A140.
Related Projects: Miscellaneous, Stars (new), and Football3 (new). IntroOOP and MoreApps feature in optional assignments.
Assignment: Improve Class VendingMachine
You may recall that after we created class VendingMachine
in Chapter 3.5, we
raised some questions about the quality of its sellBottle
method.
Task description
Locate VendingMachine
in project Miscellaneous and modify it. Edit sellBottle
so that
it no longer returns minus one to signify a failed purchase. Instead, the method’s return
type should be Option[Int]
; the method should return None
if no bottle was sold and
the amount of change wrapped in a Some
object if a bottle was sold.
Submission form
A+ presents the exercise submission form here.
Assignment: Fix Class Member
Task description
The same project contains class o1.people.Member
, a separate example. Examine its
program code and documentation. Notice that the code doesn’t match the docs: the methods
isAlive
and toString
are missing. Write them.
Hints
- The
match
command is one way to solve this assignment, but if you use a couple of the methods onOption
, you should be able to come up with a still simpler solution. The methods were introduced in the previous chapter (4.3). - When trying your class in the REPL, remember to
import
from the packageo1.people
.
Submission form
A+ presents the exercise submission form here.
Assignment: Implement Class Passenger
Task description
A class Passenger
is listed in the documentation for o1.people
. No matching code
has been given, however. Implement the class.
Instructions and hints
- The class uses another class,
TravelCard
. That class has been provided for you. Use it as is; don’t change it. - The documentation details
Passenger
’s constructor parameters and their corresponding public instance variables. You won’t need to define any private instance variables for this class. - As stated in the Scaladocs, passengers should have an instance
variable of type
Option[TravelCard]
; that is, each passenger has zero or one travel cards.Option
works as a wrapper for our custom classTravelCard
. - You need to create a file for class
Passenger
withino1.people
. Here’s how:- Right-click the package in Eclipse’s Package Explorer and select New ‣ Scala Class. A small dialog pops up.
- Eclipse will set up the file for you if you enter some
additional information in the dialog. Under Name,
enter
o1.people.Passenger
as the name of the class. - There is no need to edit the other fields. Hit Finish. A new file will show up in the editor, with a bit of starter code.
- Refer to Chapter 4.3 for tools that you can use to implement the methods.
Submission form
A+ presents the exercise submission form here.
Assignment: Improve Class Order
There’s nothing new about the problem below, but it provides further practice on
Option
s and match
. We recommend it especially if you had difficulty with the
above assignments. You can also come back to practice on this problem if you run
into trouble later on.
Additional practice
Return to class Order
in project IntroOOP.
There was a tiny optional assignment in
Chapter 2.6 where the description
method in class Order
was replaced by a toString
method. If you didn’t do that
then, do it now: rename description
to toString
and write
override
in front.
Now edit the class as follows:
- Add a constructor parameter
address
with the typeOption[String]
. Also introduce a correspondingval
instance variable. This variable will to store a postal address that is (possibly) assosiated with the order. - Add a parameterless, effect-free method
deliveryAddress
. It should return aString
that indicates where the order should be delivered. This will be either the address associated with the order, if there is one, or the orderer’s personal address, if the order isn’t associated with an address. - Edit the
toString
method so that there’s an additional bit at the end: a comma and a space", "
followed by either"deliver to customer's address"
or"deliver to X"
, where X is the address associated with the order.
A+ presents the exercise submission form here.
Assignment: Convenient Keyboard Control
The next optional assignment is meaningful only if you have done the Trotter
assignment
from Chapter 3.6. Of course, you could do that assignment now if you didn’t already.
Direction.fromArrowKey
Package o1
contains not just the class Direction
but also a singleton object
of the same name. The singleton object has a convenient
method fromArrowKey
, whose behavior is illustrated below.
val exampleKey = Key.UpexampleKey: o1.Key.Value = Up Direction.fromArrowKey(exampleKey)res0: Option[Direction] = Some(Direction.Up) Direction.fromArrowKey(Key.X)res1: Option[Direction] = None
As you see, the method returns the direction that corresponds to the given key
on the keyboard. It wraps the return value in an Option
, because only some keys
correspond to a direction.
Edit TrotterApp
’s onKeyDown
method in project MoreApps. The method should do the
same job as before but you can write a simpler and less redundant implementation
with fromArrowKey
.
A+ presents the exercise submission form here.
Assignment: Star Maps (Part 1 of 4: Basic Star Info)
Introduction: stars and their coordinates
Let’s write a program that displays star maps: views of the night sky. A star map contains a number of stars; stars may also be linked together to form constellations.
In this first part of the assignment, we won’t be drawing anything yet. We’ll begin by creating some tools for representing individual stars.
Fetch the project Stars. For now, we’ll concentrate on classes StarCoords
and Stars
:
you use the former, which is already implemented, to implement the latter. Study the
two classes’ documentation; don’t mind the other classes now.
As indicated in the Scaladocs, our program needs to work on two different sorts of two-dimensional coordinates:
- A star’s location on a two-dimensional star map is represented as
a
StarCoords
object.- For this, we use a coordinate system like the one you know from math class, with values of y increasing towards the top.
- We assume that all values of x and y have been normalized so that they fall within the interval [-1.0...+1.0]. (See the illustration.) The two coordinates represent a star’s location in the visible sky independently of the size of any picture that may depict the sky.
- On the other hand, you can use the method
toImagePos
of aStarCoords
instance to produce aPos
that represents the star’s location within a particularPic
. These coordinates increase, like the otherPos
coordinates that we’ve used, right- and downward from the top left-hand corner. They indicate which pixel the star’s center should appear at within a largerPic
of the entire sky that has a known width and height.
Task description
- Read the above and the Scaladocs. Make sure you understand
the two coordinate systems we need. Make sure you understand
what
toImagePos
inStarCoords
accomplishes. - Then implement the missing methods of class
Star
so that their behavior meets the Scaladoc specification.
Instructions and hints
Here is an example of how your
Star
class should work:import o1.stars._import o1.stars._ val unnamedStar = new Star(28, new StarCoords(0.994772, 0.023164), 4.61, None)unnamedStar: o1.stars.Star = 28 (x=0.99, y=0.02) unnamedStar.posIn(rectangle(100, 100, Black))res2: o1.world.Pos = (99.7386,48.8418) unnamedStar.posIn(rectangle(200, 200, Black))res3: o1.world.Pos = (199.4772,97.6836) val namedStar = new Star(48915, new StarCoords(-0.187481, 0.939228), -1.44, Some("SIRIUS"))namedStar: o1.stars.Star = 48915 SIRIUS (x=-0.19, y=0.94)
You don’t actually have to implement the required math yourself if you make good use of
StarCoords
.You also don’t need to round a star’s coordinates even though
toString
returns them in rounded form. ThetoString
method inStarCoords
already does that for you, so use it.
Submission form
A+ presents the exercise submission form here.
Assignment: Star Maps (Part 2 of 4: Seeing Stars)
Task description
The Stars project also includes the singleton object o1.stars.io.SkyPic
, whose methods
we’ll use to generate images of star maps.
In this short assignment, we’ll focus on placeStar
, a method that takes a picture and
a star and returns a new version of the picture with the star’s picture drawn on top.
For instance, let’s say we want a picture that contains pictures of these two stars:
val unnamedStar = new Star(28, new StarCoords(0.994772, 0.023164), 4.61, None)unnamedStar: o1.stars.Star = 28 (x=0.99, y=0.02) val namedStar = new Star(48915, new StarCoords(-0.187481, 0.939228), -1.44, Some("SIRIUS"))namedStar: o1.stars.Star = 48915 SIRIUS (x=-0.19, y=0.94)
The following code should do the trick.
val darkBackground = rectangle(500, 500, Black)darkBackground: Pic = rectangle-shape val skyWithOneStar = SkyPic.placeStar(darkBackground, unnamedStar)skyWithOneStar: Pic = combined pic val skyWithTwoStars = SkyPic.placeStar(skyWithOneStar, namedStar)skyWithTwoStars: Pic = combined pic skyWithTwoStars.show()
placeStar
lacks an implementation. Read its specification in the Scaladocs and implement
it in SkyPic.scala
where indicated.
Instructions and hints
- This part-assignment should be very easy if you use the tools introduced in Part 1 above.
- After writing the method, you may reflect on what we have and
have not accomplished so far: we can now display individual stars
in a star map, but it would take a lot of manual work to display a
map with more than a sprinkling of stars.
- It would be much more convenient if we could load star data into our program from a repository of some sort. And that is indeed what we will do soon enough.
- Feel free to take a look at the folders
test
andnorthern
and especially thestars.csv
files in those folders. - We’ll return to this program in Chapter 5.2.
Submission form
A+ presents the exercise submission form here.
Assignment: Football3
Our football-scores application (of Chapter 3.5) was already rewritten once (in Chapter 4.2), and now we’re going to mess with it again.
Task description
Fetch Football3. Study its documentation. Note that class Match
is now somewhat
different than before and there is a new class, Season
.
Implement the two classes so that they meet the new specification.
Instructions and hints
- You can copy and paste your
Match
implementation from Football2 into Football3 and work from there. The only changes you need to make to that class are:- In addition to
winnerName
, there is a new method named simplywinner
, which returns anOption
. winningScorerName
no longer exists. It’s replaced bywinningScorer
, which returns anOption
.
- In addition to
- Once you have
winner
working, you can use it to simplify the implementation ofwinnerName
. - Remember:
Match
with a capital M is a programmer-chosen name for a class that represents football matches.match
is a Scala command (that can’t be used as a name; it’s a reserved word). Mix up these two, and you may get some interesting error messages. - You can again use
FootballApp
for testing. Once you’ve implementedSeason
, the app’s main window will display season statistics and a list of matches. - For implementing
Season
you may find it useful to review Chapters 4.2 and 4.3 and the GoodStuff application.- For working out the biggest win, you can
use an instance variable of type
Option
as a most-wanted holder, much like we did in classCategory
. - There are alternative techniques for solving the problem that we haven’t covered yet (such as loops; Chapter 5.5). You aren’t forbidden from using them if you happen to know how, but there is no need to.
- For working out the biggest win, you can
use an instance variable of type
- Did you notice that
goalDifference
can return a negative number?
Submission form
A+ presents the exercise submission form here.
Something to think about
The documentation goes out of its way to say that matches added to
a Season
are assumed to have finished and no more goals will be
added to those matches. What happens if you go against this
assumption? What would it take to reimplement the program so that
we prevent that from happening?
Further Reading: The Flexible match
Command
The boxes below tell you more about the match
command, which we’ve used only for
manipulating Option
objects. This additional information isn’t required for O1 but
should interest at least those readers who have prior programming experience and wish
to explore Scala constructs in more depth. Beginners can perfectly well skip this section
and learn about these topics at a later date.
match
is a pattern-matching tool
The general form of match
is:
expression E match { case pattern A => code to run if E’s value matches pattern A case pattern B => code to run if E’s value matches pattern B (but not A) case pattern C => code to run if E’s value matches pattern C (but not A or B) And so on. (Usually, you’ll seek to cover all the possible cases.) }
None
and
Some
patterns, but many more kinds of patterns are possible.
Some of them are introduced below.Primitive match
ing on literals
Suppose we have an Int
variable called number
. Let’s use match
to examine
the value of the expression number * number * number
.
val cubeText = number * number * number match {
case 0 => "number is zero and so is its cube"
case 1000 => "ten to the third is a thousand"
case otherCube => "number " + number + ", whose cube is " + otherCube
}
match
checks the patterns in order until it finds one that
matches the expression’s value. Here, we have a total of
three patterns.Int
literals. The first case is a match if the cube
of number
equals zero; the second matches if the cube equals one
thousand.otherCube
. Such a pattern will match any value;
in this example, the third case will always be selected if the
cube wasn’t zero or one thousand.Below is a similar example where we match on Boolean
literals rather than Int
s.
These two expressions do the same job:
if (number < 0) "negative" else "non-negative"
number < 0 match {
case true => "negative"
case false => "non-negative"
}
We can accomplish all that by chaining if
s and else
s (Chapter 3.4), too.
Matching on literals and variables has not yet demonstrated the power of match
.
But see below.
Question from student: Is match
roughly the same as Java’s switch
?
Java and some other programming languages have a switch
command that selects among
multiple alternative values that an expression might have. Scala’s match
is similar
in some ways. However, switch
can select only a case that corresponds to a specific
value (as in our cubeText
example), whereas match
provides a more flexible
pattern-matching toolkit. Perhaps the most significant differences are that match
can:
- make a selection based on an object’s type; and
- “take apart” the object and automatically extract parts of it into variables defined in the pattern.
Examples of both appear later in this chapter.
Guarding a case with a condition
You can associate a pattern with an additional condition (a pattern guard) that needs to be met for a match to happen.
val cubeText = number * number * number match {
case 0 => "the number is zero and so is its cube"
case 1000 => "ten to the third is a thousand"
case other if other > 0 => "positive cube " + other
case other => "negative cube " + other
}
if
keyword, but this isn’t a standalone if
command.Underscores in patterns
An underscore pattern means “anything” or “don’t care what”. Here are a couple of examples.
number * number * number match {
case 0 => "the number is zero and so is its cube"
case 1000 => "ten to the third is a thousand"
case _ => "something other than zero or thousand"
}
match
ing on data types
In the preceding examples, the patterns corresponded to different values of the same type. In this example, the patterns match values of different types:
def experiment(someSortOfValue: Any) =
someSortOfValue match {
case text: String => "it is the string " + text
case number: Int if number > 0 => "it is the positive integer " + number
case number: Int => "it is the non-positive integer " + number
case vector: Vector[_] => "it is a vector with " + vector.size + " elements"
case _ => "it is some other sort of value"
}
Any
, which means
that we can pass a value of any type as a parameter. (More on
Any
in Chapter 7.3.)vector
has the type Vector
,
so we can use the variable to call the matching vector’s size
method.Using match
to take apart an object
One of match
’s most appealing features is that you can use it to destructure the object
that matches a pattern, extracting parts of it into variables. A simple example is the
“unwrapping” of a value stored within an Option
:
vectorOfNumbers.lift(4) match {
case Some(wrapped) => "the number " + wrapped
case None => "no number"
}
Some
will have some value inside it. That value is
automatically extracted and stored in the variable wrapped
.This feature of match
can be combined with the others listed above. Below, we take
apart an Option
and, at the same time, try to match its possible contents to one of
several cases:
vectorOfNumbers.lift(4) match {
case Some(100) => "exactly one hundred"
case Some(wrapped) if wrapped % 2 == 0 => "some other even number " + wrapped
case Some(oddNumber) => "the odd number " + oddNumber
case None => "no number at all"
}
You can destructure an object in this fashion only if the class defines how to do that
for objects of that type. Many of Scala’s library classes come with such a definition,
Some
included. Similarly, O1’s own library defines that a Pos
object can be
destructured into two coordinates:
myPos match {
case Pos(x, y) if x > 0 => "a pair of coordinates where x is positive and y equals " + y
case Pos(_, y) => "some other pair of coordinates where y equals " + y
}
Pos
, two numbers can be extracted from it.
Store them in local variables x
and y
. If x
is positive,
this case matches.”Pos
. Discard the first and
store the other in a variable y
.” Any Pos
will match this
pattern and this branch will be chosen every time if the first
one isn’t.So how do you define how objects of a particular type can be taken apart? The easiest way is to turn a class into a so-called case class (tapausluokka). Here’s a simple example:
case class Album(val name: String, val artist: String, val year: Int) {
// ...
}
case
, this is like a regular class
definition.match
ing.You can then use the case class like so:
myAlbum match {
case Album(_, _, year) if year < 2000 => "ancient"
case Album(name, creator, released) => creator + ": " + name + " (" + released + ")"
}
Search online for Scala pattern matching and Scala case class for more information. As noted, you aren’t required to use these language features in O1.
Further Reading: Option.get
This optional section introduces a method on Option
s that can seem deceptively handy
but that you should avoid.
The dangerous get
method
One way to open up an Option
wrapper is to call its parameterless get
method.
Try it.
We could have used get
instead of match
to implement addExperience
in
class Category
:
def addExperience(newExperience: Experience) = {
this.experiences += newExperience
val newFave =
if (this.fave.isEmpty)
newExperience
else
newExperience.chooseBetter(this.fave.get)
this.fave = Some(newFave)
}
isEmpty
checks whether an old favorite exists.get
the old favorite from its wrapper. Since we do this
only in the else
branch, we know we’re not dealing with
None
.However, if we use get
, we’re faced with some of the same problems that we had with
null
: if we call get
on None
, our program crashes. It’s up to the programmer to
ensure that get
is only called on a Some
. This is easy to forget.
As one student put it:
At least you shouldn’t just rush to open a wrapper, as get
does. It’s better to
be prepared for possible disappointment and avoid the tears.
It’s good to know that get
exists; you may see it used in programs written by
others. Avoid using it yourself. There is always a better solution such as match
,
getOrElse
, or one of the methods from Chapter 8.2.
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 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 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 behind O1Library’s tools 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+ 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.
match
can examine the value of any expression. In technical terms, this examination is a form of pattern matching (hahmonsovitus): the expression’s value is compared to...