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 2.2: Inside an Object

../_images/person06.png

Introduction

In this chapter, we’ll use Scala to define the behavior of individual objects. As it turns out, this endeavor calls for skills that you already have. This is because an object definition has two main parts:

  • An object’s methods are functions attached to the object. To define these functions, we can use techniques that are largely familiar from earlier chapters.

  • An object needs a way to to keep track of its attributes. We can do this with another familiar construct: variables.

What’s new here is how we attach functions and variables to an object.

Our Goal: An Employee Object

Let’s create a singleton object that represents an individual employee in an (imaginary) accounting system. But first, let’s look at how we’ll use the object once we have it.

An employee has attributes such as a name and a monthly salary. The code that defines the object also defines the values of these attributes. We can request those values from the object:

employee.nameres0: String = Edelweiss Fume
employee.monthlySalaryres1: Double = 5000.0

The effectful method raiseSalary takes a number as a parameter. This number is a multiplier that is applied to the old salary. For instance, the following command, gives the employee a ten-percent raise:

employee.raiseSalary(1.1)

That command changed the object’s state, which we can confirm:

employee.monthlySalaryres2: Double = 5500.0

The effect-free method ageInYear takes a year as a parameter. It sends back a return value that lets the caller know how old the employee will be in that year. The employee object knows its year of birth and uses it to compute the age:

employee.ageInYear(2025)res3: Int = 60

The object has a working time, which is expressed as a decimal number. A value of 1.0 means full-time work (i.e., 100% working time):

employee.workingTimeres4: Double = 1.0

We can adjust the working time simply by assigning a new value to the attribute. (This is similar to what we did in the radio example from Chapter 2.1.) In effect, we’re sending the object a message: “Set your working time to 0.6.” The value 0.6 indicates part-time work — 60% of a full-time hours:

employee.workingTime = 0.6employee.workingTime: Double = 0.6

The effectful monthlyCost method determines the employee’s monthly cost to their employer. Our object computes this cost as the product of the monthly salary (now 5500 euros), the working time (now 60%), and a multiplier to cover additional costs. The multiplier is provided as a parameter; in the example below, it is 1.3:

employee.monthlyCost(1.3)res5: Double = 4290.0

Finally, the description method returns a string that summarizes the employee’s main characteristics in English. This effect-free method takes no parameters, so we can call it without round brackets (just as we didn’t when we asked the object for its name and salary above). Here, we ask the object for its description and print what we get:

println(employee.description)Edelweiss Fume (b. 1965), salary 0.6 * 5500.0 e/month

Defining an Object and Methods in Code

The employee object: how it looks on the inside

Here’s the code that defines our employee object. You can also find the code in employee.scala within the IntroOOP module.

Let’s begin with an overview before we look at each method in detail.

object employee:

  var name = "Edelweiss Fume"
  val yearOfBirth = 1965
  var monthlySalary = 5000.0
  var workingTime = 1.0

  def ageInYear(year: Int) = year - this.yearOfBirth

  def monthlyCost(multiplier: Double) = this.monthlySalary * this.workingTime * multiplier

  def raiseSalary(multiplier: Double) =
    this.monthlySalary = this.monthlySalary * multiplier

  def description =
    this.name + " (b. " + this.yearOfBirth + "), salary " + this.workingTime + " * " + this.monthlySalary + " e/month"

end employee

The word object marks the start of a singleton object’s definition. The next word is a name that the programmer chose for referring to the object. Notice the colon at the end of the line, too.

Optionally, we can mark the end of the object definition with an end marker. It is customary to do so unless the definition is very short and has no blank lines. So we do that here.

Between the first and the last line, we define the object’s variables; their definitions look familiar. This is where we assign (initial) values to the variables. The value of an object’s val variable can only be examined (as in employee.yearOfBirth), but a var variable can be assigned a new value, as we did with the employee’s working time in the REPL.

Then we have the object’s methods, each defined with the def keyword. There is no rule in Scala that requires us to write the variables above the methods, but many programmers prefer to write them in that order. More on the methods below.

Indentations matter in object definitions, too. Each of the object’s variables and methods has been indented deeper than the object and end lines. And any multi-line methods’ bodies are indented one step further to the right.

The employee object: how it works on the inside

The employee’s methods in detail

def ageInYear(year: Int) = year - this.yearOfBirth

def monthlyCost(multiplier: Double) = this.monthlySalary * this.workingTime * multiplier

def raiseSalary(multiplier: Double) =
  this.monthlySalary = this.monthlySalary * multiplier

def description =
  this.name + " (b. " + this.yearOfBirth + "), salary " + this.workingTime + " * " + this.monthlySalary + " e/month"

Scala’s keyword this means “the object whose method is being executed”. Or, from the object’s perspective: “I myself”. Follow the word with a dot, and you can refer to members of the object. As shown in the animation, this is basically a parameter variable that works just like other parameter variables do. It receives its value “from the left of the dot” in each method call.

The code instructs the object: “When your method ageInYear is called, respond with a number that you get by subtracting the value of your own yearOfBirth variable from the given year.”

“Multiply your own monthly salary, your own working time, and the given multiplier (the last of which is stored in a local parameter variable during the method call).”

“Multiply your own monthly salary with the given multiplier and assign the result to be your monthly salary (replacing the old value).”

Chapter 1.7 introduced some rules and conventions for defining functions. They apply to methods, too. Don’t forget those equals signs!

description takes no parameters and doesn’t even have a parameter list.

More about this

In O1, we’ll use the word this pretty much every time we wish to refer to the members of the object that is executing a method. Technically, though, there is no requirement to use the word as often as we do.

Sometimes it is necessary to use this. As an example, below is a different version of the employee object that differs from the earlier one in just a single variable name:

object employee:

  var name = "Edelweiss Fume"
  val year = 1965
  var monthlySalary = 5000.0
  var workingTime = 1.0

  def ageInYear(year: Int) = year - this.year
  // etc.

In this version, the year of birth is stored in a variable called simply year, not yearOfBirth as in the original.

As a result, the object’s variable has the same name as ageInYear’s parameter variable.

The name year without a preceding this means the year given as a parameter.

By writing this.year, we specify that we mean the object’s variable, not the parameter. If we were to leave out this word, the method would subtract its parameter value from that very same value, and would thus always return zero.

In our original employee object, we could have omitted every this prefix, because none of the local variables had the same name as the object’s variables.

Even when it’s not required, the word this helps clarify that you’re using an object’s variables rather than local ones. We recommend that you always use this when referring to an object’s own variables, because it makes the code easier to read and understand.

To an extent, this is a matter of taste. If you wish — and if you know what you’re doing — go ahead and leave out any this keywords that don’t need to be there. Outside O1, such omissions are (unfortunately?) very common.

Careful with Indentations and Punctuation

When you define an object, you’ll need to pay attention to punctuation (special characters). You’ll also need to pay still more attention to indentations now that we have method definitions nested within object definitions.

Soon you’ll get to write an object of your own, but let’s warm up to that first. In the short assignment below, you’ll read some object definitions and assess whether they work or have an error.

Each question below presents a definition for an object named testObject — or at least an attempt at such a definition. This object doesn’t do anything interesting; in this assignment, we’ll focus on the formalities of writing code.

The test object should have a method named calculate, which takes in a number and computes and returns another number, like this:

testObject.calculate(100)res6: Int = 204

The object should also have a multiply method that takes in a string and returns a longer one that contains the original several times:

testObject.multiply("Yay")res7: String = YayYayYayYay

Answer the questions below. (One of the questions has two correct answers; pick either one of them.)

object testObject:

  val baseNumber = 4

  def calculate(number: Int) = this.baseNumber + (number * 2)

  def multiply(text: String) = text * this.baseNumber

end testObject

Does that definition produce an object whose method work as illustrated above?

object testObject:

  val baseNumber = 4

  def calculate(number: Int) =
    val twiceThat = number * 2
    this.baseNumber + twiceThat

  def multiply(text: String) =
    text * this.baseNumber

end testObject

Does that definition produce an object whose method work as illustrated above?

object testObject:

  val baseNumber = 4

  def calculate(number: Int) =
  val twiceThat = number * 2
  this.baseNumber + twiceThat

  def multiply(text: String) = text * this.baseNumber

end testObject

Does that definition produce an object whose method work as illustrated above?

object testObject:

  val baseNumber = 4

  def calculate(number: Int) =
    val twiceThat = number * 2
    this.baseNumber + twiceThat

    def multiply(text: String) = text * this.baseNumber

end testObject

Does that definition produce an object whose method work as illustrated above?

object testObject

  val baseNumber = 4

  def calculate(number: Int) = this.baseNumber + (number * 2)

  def multiply(text: String) = text * this.baseNumber

end testObject

Does that definition produce an object whose method work as illustrated above?

Your Turn: Create an Object

Next, you’ll get to write an object that represents a bank account. The object should work as shown in the following example.

The account object: an example scenario

An account has a balance (in euro cents) and an account number. We can ask the object to report this information:

account.balanceres8: Int = 0
account.numberres9: String = 15903000000776FI00

We can deposit money in the account. Here, we choose to represent sums of money as integers that correspond to euro cents. Let’s deposit 200 euros and review the balance:

account.deposit(20000)account.balanceres10: Int = 20000

Depositing a negative sum doesn’t affect the balance:

account.deposit(-1000)account.balanceres11: Int = 20000

The withdraw method takes money from the account and returns the amount that was successfully withdrawn:

account.withdraw(5000)res12: Int = 5000
account.balanceres13: Int = 15000

Withdrawals that would result in a negative balance aren’t allowed. Below, we get only 150.00 euros as we empty the account:

account.withdraw(50000)res14: Int = 15000
account.balanceres15: Int = 0

Your assignment

Write an object account that works precisely as described above. To summarize the main points:

  • It has a number, "15903000000776FI00".

  • It has a balance that is initially zero but may change.

  • It has an effectful method deposit that adds a given amount of money to the account (as long as the given parameter value is positive) and doesn’t return anything.

  • It has another effectful method, withdraw, which reduces the account’s balance by a given amount, or empties the account if the given parameter is greater than the current balance.

  • The withdraw method not only reduces the account’s balance but also returns the amount that was successfully withdrawn (as in the REPL examples above).

  • (In this assignment, you’re not required to consider the possibility that a negative number might be passed to withdraw. But if you want, you can write the method to leave the account untouched in such cases.)

Write your code within the file o1/singletons/account.scala in IntroOOP. There’s a comment in the file that shows where exactly.

How to go about it

We suggest the following approach:

  1. Study the example scenario in detail. Note the names and types of each variable!

  2. Find the marked spot in account.scala. An outline for the object definition is already there.

  3. Define the object’s variables by assigning values to them. Indent your code with two space characters per line.

  4. See if it works. Fire up your REPL in the IntroOOP module and try the expressions account.balance and account.number.

  5. Write the methods deposit and withdraw. Use the min and max you know from earlier chapters.

    • The algorithm you need for withdraw is familiar from Chapter 1.8’s penalty function. If you struggle to make withdraw return the right value, review your solution to the penalty assignment (or see the example solution).

  6. Reset the REPL session and test your methods with different parameter values. (Reminder: you may find it convenient to use the Rerun button rerun in the REPL’s top-left corner.)

  7. Submit your code only once you’re convinced that your object works as specified.

A+ presents the exercise submission form here.

Something wrong?

Once you’re done with the assignment, take a moment to reflect on the object you just defined.

Did you define a var variable named balance for your account object? If you did, that’s all good. But there’s something a bit questionable about this variable that might have already occurred to you.

We took it upon ourselves to create an account object whose balance is modified through deposit and withdraw. These method apply due diligence and prevent the balance from becoming negative. But right now, there’s nothing stopping us from commanding the object to set its balance to a negative value like this: account.balance = -100000

Obviously, this is no problem if nobody ever issues such a command. We could say that assigning a negative balance counts as an error on the part of whoever uses the account object. But there are many programmers who agree that programs should designed to minimize the risk of such mistakes.

Soon enough, in Chapter 3.2, you’ll learn how to restrict the operations on an object’s variables. Right now, you don’t need to worry about the matter.

There are styles of programming where it wouldn’t be considered problematic to make such an inappropriate command available. Scala programmers, however, tend to be among those who argue that there should be mechanisms for reducing such opportunities for error. That makes programming more productive (especially when working on large programs), and the resulting software is more reliable.

Summary of Key Points

  • Some of the objects in Scala programs are singleton objects: objects defined on an individual basis.

  • The definition of a singleton object contains variable definitions and method definitions.

    • You can define a variable to be part of an object. Such a variable can be assigned values, just as we’ve done with other variables.

    • You can define methods for an object, just as we’ve defined other functions before.

  • It’s extremely common that a method needs to use one or more of the object’s own variables or other methods. You can use Scala’s keyword this to make an object refer to itself and its own components.

  • Links to the glossary: object, singleton object; this.

An updated concept map:

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, Kai Bukharenko, Nikolas Drosdek, Kaisa Ek, Rasmus Fyhrqvist, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Kaappo Raivio, Timi Seppälä, Teemu Sirkiä, Onni Tammi, 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 appear at the ends of some chapters.

a drop of ink
Posting submission...