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 4.1: Driving Practice
Nice to Know: Shorthand Operators for Variables
When you assign a new value to, say, a gatherer or a stepper, you use the variable’s old value as part of the expression that determines the new value. When the variable’s type is numeric, the expression is often arithmetic. Here are a couple of examples:
this.balance = this.balance + amount
this.value = this.value + 1
Since such commands are common and since they contain the same code fragment twice, the designers of Scala (and other languages) have chosen to provide a way to express the same thing more succinctly. For instance, we can alternatively write the above as:
this.balance += amount
this.value += 1
The +=
operator means “Increase the value of the variable on the left by the value on
the right.” Or to be somewhat more precise: “Take the old value of the var
variable on
the left and the value of the expression on the right. Sum them and assign the result to
the variable on the left.”
(By the way, as you see, the notation +=
looks like the one you learned in Chapter 1.5
for adding a value to a buffer. This notation is used here for a different purpose: the
addition of numbers. Of course, there is an analogy between the two uses: a buffer is
updated by adding to it and a variable is updated by adding to it.)
Syntactic sugar
It’s common for programming languages to let us express the same meaning in different ways. Programmers often speak of syntactic sugar (syntaktinen sokeri), which refers to language constructs that aren’t necessary in terms of the language’s expressive power (i.e., they don’t enable new kinds of programs) but that make it more convenient or elegant to express certain things. The shorthand for variable assignment is an example of syntactic sugar.
Syntactic sugar is a matter of taste that programmers often quibble over. Since all general-purpose programming languages (such as Scala) are equal in terms of the computations they can express, one might even say that, in a sense, programming languages in general are merely syntactic sugar.
More shortcuts
There are more similar shorthand operators; see the table below for examples. The Scala toolkit automatically “expands” the abbreviations.
Longer |
Shorter |
---|---|
|
|
|
|
|
|
|
|
|
|
These operators are in common use, and we’ll also be using them in O1. You may choose to apply them in the upcoming programming assignments, or not, as you prefer. In any case, you’ll need to be able to read code that uses the operators.
A Frequently Asked Question
In some widely used programming languages (such as C and Java),
there is a still shorter way to express the adjustment of a
variable’s value by one. For example, number++
and number--
adjust a variable’s value like number += 1
and number -= 1
.
Do we have these operators in Scala, too?
Answer: No. Scala’s designers haven’t seen that bit of additional
brevity as worth pursuing. One reason is that although there are
many ways to program in Scala, one of the most common (functional
programming; Chapter 11.2) makes limited use of var
s. On the
other hand, Scala is a flexible language, and you can extend it
with “operators” of your own (Chapter 5.2).
Practice on the shortcuts
Further practice
Modify class VendingMachine
(Chapter 3.5) so that you use the
above shorthand notation in place of regular assignment statements
wherever possible.
You’ll find the class in module Miscellaneous.
CarSim
Task description
Fetch CarSim. It contains a good part of an application program. The GUI is ready for use. However, a key component is missing. Here is your task:
to study the documentation of
o1.carsim.Car
thoroughly;to write the Scala program code that implements the class, building on the given skeleton code; and
to test that your class works as specified in the Scaladocs, fixing any bugs that remain.
???
In several places within the skeleton code, there are three question
marks: ???
. This Scala expression indicates that a part of a program
is not yet implemented. Attempting to evaluate such an expression
yields a runtime error. (This will happen, for instance, if you try
calling one of the methods in the given Car
class.)
Two methods named fuel
?
Did you notice that there are two different methods named fuel
,
with different parameter lists?
Even though these are two entirely separate methods, you can use one of them to implement the other.
Further down in this chapter, there is some additional information about methods that share a name.
We recommend that you tackle the assignment in stages, as described below.
Step 1 of 6: Instance variables
You need to figure out which instance variables you need so that you can keep track of car’s state between method calls. Some of the variables should be private so that users of the class can’t arbitrarily modify their values.
The Scaladocs spell out the constructor parameters, as does the given code. In the code,
some of the parameters have also been (correctly) associated with a corresponding instance
variable. What are the other key attributes of a Car
; that is, what other info does a Car
object need to track in order for its methods to work? What kind of variables would
help you store that information? What are the types and roles of each variable?
It’s not necessary to come up with every last variable right away. You can return to this step later if the need arises.
Stick to the specified interface, please
Do not add public members to the class beyond those requested. On the other hand, you’re free to add private members as you see fit.
This guideline applies to all of O1’s programming assignments that specify public interfaces for you to implement.
An optional hint about instance variables
Use instance variables to track information that needs to persist between method calls. This includes each car’s fuel consumption rate and fuel-tank size, both of which are already defined as instance variables in the given code.
Several methods operate on the car’s current fuel level. Store that information in an instance variable so that it’s not lost between method calls.
The methods location
and drive
access the car’s current
location, which you’ll also need to store as part of the car
object.
metersDriven
and drive
need to know how far the car has been
driven in total.
You don’t need additional instance variables beyond those. If you want to store some information temporarily while a method runs, use a local variable.
Step 2 of 6: The easier(?) methods
The drive
method is the most complex of the bunch. One way to work through this
assignment is to leave it aside for now and implement the other methods first. (But
do make sure that you read drive
’s documentation already, nevertheless, so that
you know what its purpose is.)
An optional hint for location
and metersDriven
As long as you have the car’s location and total meters stored in instance variables, these corresponding methods should be extremely simple to implement.
Note: Even if you haven’t yet implemented the drive
method, you
can assume, at this stage, that drive
will eventually work as
intended, updating the car’s location and total meters driven. So
the only thing these other two methods have to do is return a value;
these methods are effect-free.
An additional hint for the fuel
methods
The single-parameter fuel
method is very similar to
some methods that you’ve written in earlier assignments (cf.
account withdrawals). Carefully note what the method should
return. Use a local variable as you compute the return value.
The parameterless fuel
is easy to implement: call the
other fuel
and pass a large enough number as a parameter.
Step 3 of 6: Test your class as a separate component
Try running the test program carTest
that uses Car
separately from the rest of the
CarSim app. Apart from driving, the test program should now produce output that makes
sense.
You can edit the test program to improve coverage. You can also experiment with Car
in the REPL.
Step 4 of 6: Test your class as part of CarSim
Launch CarSim via the app object o1.carsim.gui.CarSim
. Try adding cars and fueling them
through the GUI. There are some usage instructions at the bottom of the GUI window.
Step 5 of 6: Implement drive
As the documentation says: in this assignment, you can assume that each car moves in a straight line, “as the crow flies”. Sometimes, a car will fail to reach the destination as its fuel runs out.
In case you have trouble with the math required to determine where a car stops, this may help:
Even though CarSim uses the
x
andy
of aPos
to represent latitude and longitude on the Earth’s round surface, this assignment has been set up for you so that you can continue to think aboutPos
objects in terms of ordinary, flat, two-dimensional coordinates.Forget programming for a while. Draw a diagram of the car’s movement, and solve the math problem first. Then consider how to express the solution as Scala.
You don’t have to model the car’s velocity. Just take care of the things described in the Scaladocs.
An optional hint about the math
You won’t need any trigonometry. Basic arithmetic will do: multiplication, addition, division, and subtraction.
You may also draw on the methods of class Pos
. The
already-familiar xDiff
and yDiff
methods could be of
use, and you may find other methods described in the docs helpful as well.
Further hints
Note the units: distances in meters, fuel consumption in liters per hundred kilometers.
The trickiest bit may be to compute the car’s new location when fuel runs out. Compute the ratio between the distance the car actually drives and the distance it was supposed to drive (the method’s second parameter). Since we’re modeling the car’s movement as a straight line towards the destination, the same ratio applies to each of the car’s two coordinates.
Step 6 of 6: More testing
Test drive
. Again, you may wish to test the method first in the separate test program,
then as part of the full app.
Play with CarSim.
How do the cars drive about?
You may be wondering how the cars of CarSim twine their way on the map even though your own implementation was so straightforward.
The CarSim application does use your code, but it’s clever about how
it does that: it represents the complex route between the starting
location and the destination as tiny straight segments and calls your
drive
method for each segment in quick succession. If you want, you
can use CarSim’s Settings menu to display the segments.
And what about finding the correct route? The application does that with the help of an online service by Here.com, which resembles the perhaps more familiar location services provided by Google. The map images also come from an online API.
If you want, you can take a look at the other CarSim components, which accomplish the above. However, be warned that if you’re a beginner programmer, the code doesn’t make for easy reading. In any case, you can take from this assignment the lesson that programs can be built upon powerful external services.
A+ presents the exercise submission form here.
On Method Overloading
Methods that share a name
The above assignment featured two methods called fuel
. One took a single parameter, the
other just an empty parameter list. As you’ll know by now, a class (or singleton object)
can perfectly well have multiple methods of the same name. Their parameter lists must be
different, however.
This sharing of names is known as overloading (kuormittaminen), and we already
saw an example of it in the two verticalBar
functions of Chapter 1.7; the only
difference is that now you overloaded a method name within a class you wrote.
A method’s name and parameter list are part of its signature (puumerkki). When
you call an object’s method — someObject.methodName(parameters)
— which method
actually gets called depends on which method’s signature matches what you wrote at the
call site. You can’t define two methods with identical signatures in the same class.
In principle, each overloaded method could do something completely different from the other methods of that name, but in practice, overloading should be used for creating variations of the same functionality, as in the fueling methods above.
Overloading and return types in Scala
Chapter 1.8 brought up that you can annotate any Scala function with its return type. In some contexts, these annotations are mandatory. Perhaps the most common example of such a context is an overloaded method that calls another method of the same name. We must assist Scala’s automatic type inference by explicitly defining the type of the method that calls its namesake. You can confirm this for yourself as follows.
Did you implement the parameterless fuel
method by calling the single-parameter fuel
,
as suggested? If you did, now try removing the type annotation (the colon and the word
Double
) in the method header. You’ll be presented with the error “overloaded method
fuel
needs result type”.
The phenomenon is easy to observe in the REPL, too:
class MyClass: def myMethod(first: Int, second: Int) = first + second def myMethod(number: Int) = this.myMethod(number, number)-- Cyclic Error: | def myMethod(number: Int) = this.myMethod(number, number) | ^ | Overloaded or recursive method metodi needs return type class MyClass: def myMethod(first: Int, second: Int) = first + second def myMethod(number: Int): Int = this.myMethod(number, number)// defined class MyClass
This business with the return type is just a little quirk of Scala’s; it has no broader significance to us. Fortunately, if you forget about it, you get a clear error message. Also remember that annotating a method with a return type is always a valid choice.
Additional Material: Accessor Methods
Trouble with naming: instance variable vs. method
Here are a few familiar example programs that all have a “look-but-don’t-touch” aspect:
In the
Order
class of Chapter 3.2, we wanted an order’s total price to be available to the class’s user (via the methodtotalPrice
). However, we did not wish for the user to directly modify that information (by assigning to variablesumOfPrices
). So we used a privatevar
variable and had the public method return its value.We applied the same idea to FlappyBug, where the movements of the bug and the obstacle are restricted by their respective classes and recorded internally in their
currentPos
variables. The classes’ user has access to their publicpos
methods.In Chapter 3.5, the number of goals scored by each team was recorded in private variables (
awayCount
,homeCount
). ClassMatch
’s interface provided access to these values through two corresponding methods (awayGoals
,homeGoals
).Perhaps you also did something similar in the CarSim assignment above?
All the above classes have some data that is stored internally in a variable but accessed by the user via a method. The creator of the classes needs to pick two names: one for the variable, one for the method. Each is related to the same domain entity.
There is no single best solution. Different programmers adopt different solutions in different cases.
If you can’t come up with two good names, use the better name for the public method and the worse name for the private variable. By doing this, you prioritize the ease of use of the class.
Some programmers like to use abbreviations in the names of private instance variables. If you do, watch out that your code doesn’t become unreadable!
Some programmers like to mark the private variables with a prefix
such as mBuyer
(where m
= member of the class) or _buyer
.
Some other programming languages promote the following style:
buyer
(variable) vs. getBuyer
(method). This convention doesn’t
put usability first, however. In Scala, it’s seldom used.
In most of O1’s programming assignments, the public names have been specified for you in advance. You’re free to come up with the private names.
By the way: notice how this naming headache arises only when we use
var
s. When it comes to val
s, things are simpler, because you
can’t assign a new value to a val
and there’s no need for a separate
method.
Occasionally asked question: Don’t we use “getters” and “setters” in Scala, like we do in Java?
In the examples listed above, we associated a var
with a so-called
“getter” method that returns the var
’s value. However, we haven’t
defined such a “getter” for nearly all of the instance variables in
our programs.
In Scala programs, it’s common to leave instance variables public when they represent externally visible attributes of objects. “Getters” are more the exception than the rule.
Consider this example:
class Profile(val name: String, var status: String):
// methods go here
The variables that record the profile’s name and status are public. Their values can be examined by any programmer who uses the class; that user can also assign a new value to the latter variable. Had we written this class in “Java style”, it would look like this:
class Profile(private val name: String, private var status: String):
def getName = this.name
def getStatus = this.status
def setStatus(status: String) =
this.status = status
// additional methods go here
end Profile
We have separate methods for examining and updating a profile object’s attributes.
“Getters” and “setters” like this are ubiquitous in Java programs (and in various other languages that support object-orientation). There are countless style guides that tell the beginner Java programmer to avoid public instance variables like the plague. An earlier Java-based incarnation of O1 also commanded students thus:
Why? And why doesn’t that commandment apply to Scala, so that we can write public variables with impunity? Does Scala in fact actually have implicit “getters” of a sort behind the curtain, even though we don’t explicitly define them?
If you’re interested, you can read up on these questions online. Here are a couple of links to get you started.
Reasoning behind the Java convention: Why use getters and setters?
Reasoning behind the Scala convention: Getters and Setters in Scala
Additional Material: Constructors and Parameters
Overloading constructors
We just discussed overloading methods. You can also overload a constructor; that is, you can define multiple ways of instantiating a class from different parameters. You won’t need to in O1, though.
Let’s write a small class as an experiment. First of all, we should be able to instantiate the class so that we pass in a word and a number as constructor parameters:
Experiment("cat", 100)res0: Experiment = an object whose word is cat and whose number is 100
Nothing new so far. This class matches the requirement:
class Experiment(val word: String, val number: Int):
override def toString = "an object whose word is " + this.word + " and whose number is " + this.number
But what if we also wish to be able to instantiate the class so that we pass in just a number, in which case the class will use the default word “llama”?
Experiment(10)res1: Experiment = an object whose word is llama and whose number is 10
Moreover, we’d like to be able to just pass in a word and use a default number of 12345:
Experiment("dog")res2: Experiment = an object whose word is dog and whose number is 12345
We can enable these alternative means of instantiation by overloading the class’s constructor. Scala has a particular notation for this:
class Experiment(val word: String, val number: Int):
def this(word: String) = this(word, 12345)
def this(number: Int) = this("llama", number)
override def toString = "an object whose word is " + this.word + " and whose number is " + this.number
end Experiment
The class header still defines the primary, “normal” way to create a new instance. In our example, this calls for two parameters.
def this
denotes the start of an alternative
constructor. The round brackets that follow
list the parameters that are required for that
instantiation method.
The expression this(…)
means, roughly,
“Create an instance in the normal fashion,
passing in the bracketed information as
constructor parameters.” For example,
here we say that if just a word has been
supplied, we create the instance using that
word and the number 12345.
If flexible instantiation is the goal, constructor overloading is not the only solution, or necessarily the most convenient one: see the next box.
Default parameter values
Either Experiment2("llama", 453534)
or simply Experiment2("llama")
will instantiate this class:
class Experiment2(val word: String, val number: Int = 12345):
override def toString = "an object whose word is " + this.word + " and whose number is " + this.number
If the caller doesn’t supply the second parameter, the default parameter value (oletusparametriarvo) associated with the parameter variable applies instead.
You can set a default value on a method parameter just as you can on a constructor parameter.
Omitting val
/var
and its effects on privacy
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:
So, if you don’t turn a constructor parameter into an instance variable, the program will crash if later, when you use the object, you try to call a method that tries to access the constructor variables, right?
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:
What happens if the constructor parameter
first
wasn’t preceded by val
or var
but...
... a method in the class nevertheless tries
to access first
?
Or, as another example: what if the method
were to access 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 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
This code is otherwise identical to the
previous one but 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...
... if you nevertheless do access such a
non-val
, non-var
constructor parameter
in a method, Scala implicitly defines a
private instance variable on your class.
The above code works exactly as if you had defined the parameter as
private val first: Int
. In O1, we won’t be writing code like that.
Summary of Key Points
Some applications use interfaces (APIs) created by external providers. Some such interfaces are available as services on the internet.
For example, the application in this chapter used an online mapping service.
You’ll learn more about accessing network services in O1’s follow-on courses.
There are shorthand notations (e.g.,
+=
,*=
) for some common commands that manipulatevar
variables.You can overload a method name: a class can have multiple distinct methods with the same name but different parameter lists and different implementations.
In Scala, if you call an overloaded method from one of its namesake methods, you must explicitly mark the calling method’s return type.
Links to the glossary: API; to overload, signature; syntactic sugar.
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 for this page
The CarSim program was made compatible with Here.com’s mapping services by O1’s head assistant emeritus Teemu Sirkiä.
This chapter does injustice to music by The Beatles. Thank you and sorry.
The instance variables
name
andstatus
are private.