This course has already ended.

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 4.1: Driving Practice

About This Page

Questions Answered: Will I soon be able to write a Scala class fluently?

Topics: A synthesis of topics from earlier chapters. Additionally: Shorthand operations on vars. A few words on overloading method names.

What Will I Do? Program and play with the resulting map-based car simulator.

Rough Estimate of Workload:? Three hours? Four? The main assignment involves some relatively independent programming work, and there’s a chance you get stuck. We recommend that you reserve a good chunk of time, carefully read the tips you’re given, and make use of O1’s forums so that it won’t take excessively long. If you don’t get stuck, you might be done in much less time.

Points Available: A5 + B80.

Related Modules: CarSim (new).

../_images/person08.png

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
value = value + another value += another
value = value - another value -= another
value = value * another value *= another
value = value / another value /= another
value = value % another value %= another

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 10.2) makes limited use of vars. 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

Study the following piece of code and think about the values assigned to number at each step.

var number = 5
number += 1
number *= 3
number += number
number -= 2 * (number - 13)

In the field below, enter the five integers that the variable stores after each line is executed, in order. Enter each number on a line of its own.

The operators += and *= can also be applied to strings.

Study the following code.

var text = ""
text += "<e--gg#h>ed--"
text += "<h>f#-<h>de"
text *= 3
text += "/135"
play(text)
Side note: In this example, the variable is initialized to the empty string (tyhjä merkkijono), which is a string that contains no characters at all (not even whitespace or punctuation). An empty string literal is written as two double quotation marks with nothing in between. Even though the string is empty, you can use it in string operations such as + and +=, as illustrated above.

What string does the above code pass to the play function? Find out and paste the result here.

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

../_images/carsim.png

In the CarSim application, the user can direct the movements of cars on a world map. Each car is illustrated as a colored circle.

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 is a Scala expression for a part of a program that is as yet unimplemented. 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

Show the hintHide the hint

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

Show the hintHide the hint

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

Show the hintHide the hint

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.

An optional hint for fuelRatio and fuelRange

Show the hintHide the hint

Simple arithmetic will do the trick. Just make sure you use the right units: the percentage should be between zero and a hundred; the car’s “range” is measured in meters (not hundreds of kilometers).

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:

  • Despite the fact that CarSim uses the x and y of a Pos 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 about Pos 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

ShowHide

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 distance method will be of use, and you may find other methods described in the docs helpful as well.

Further hints

ShowHide

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 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 the fact 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 method(first: Int, second: Int) = first + second
  def method(number: Int)             = this.method(number, number)
}<console>:13: error: overloaded method method needs result type
       def method(number: Int) = this.method(number, number)
                                            ^
class MyClass {
  def method(first: Int, second: Int) = first + second
  def method(number: Int): Int        = this.method(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: On 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 method totalPrice). However, we did not wish for the user to directly modify that information (by assigning to variable sumOfPrices). So we used a private var 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 public pos methods.
  • In Chapter 3.5, the number of goals scored by each team was recorded in private variables (awayCount, homeCount). Class Match’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, make sure you still keep your code readable.

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 and you’re free to come up with the private names.

By the way: notice how this naming headache arises only when we use vars. When it comes to vals, 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
}
The instance variables name and status are private.
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:

../_images/java_xi-en.png

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.

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:

new 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”?

new 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:

new 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

}
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 new Experiment2("llama", 453534) or simply new 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.

Named parameters

Default parameter values are sometimes convenient but you won’t need to write methods that have them in O1.

Also in the same category are named parameter values, commonly known as named arguments.

Challenging reads

If you have prior programming experience, you may wish to do a web search for builder pattern. You should be able to find some more challenging material on constructor parameters.

The article Type-Safe Builder Pattern in Scala is an interesting read but quite unsuitable for beginners.

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 manipulate var 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 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 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.

a drop of ink
Posting submission...