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.3: Classes of Objects

../_images/person06.png

Classes

When a human sees a chair, the experience is taken both literally as “that very thing” and abstractly as “that chair-like thing”. Such abstraction results from the marvelous ability of the mind to merge “similar” experience, and this abstraction manifests itself as another object in the mind, the Platonic chair or chairness.

—Dan Ingalls

Imagine filling in a tax form on paper. Obviously, you won’t start with a blank page: you’ll use a predesigned sheet that tells you what information you’re expected to provide. It would be incredibly inconvenient if each taxpayer had to create the form template, too, since everyone provides the same kind of information. It makes sense to use a standard template and make as many copies as needed to be filled in.

Now recall from Chapter 2.1 the GoodStuff application and the imaginary course enrollment app, both of which involved multiple communicating objects. Many of those objects were similar to each other: there were multiple courses, multiple students, and multiple experience objects. Similar objects stored similar information and behaved the same way; each course object, for example, stored a course code, a classroom, and a list of enrolled students.

Just as with tax forms, it would be both unnecessary and inconvenient to define every single object on an individual basis, as we’ve done so far.

Using only singleton objects would have other problems as well. The programmer is supposed to detail each singleton object in program code, but how can they know, at the time of writing, how many objects of each kind are needed for each future program run? In most cases, they simply cannot know. The required objects and their specific attributes are determined dynamically at runtime and may depend on input from end users. In the GoodStuff app, for example, the user defines how many experience objects to create and what those objects’ attributes are.

What a programmer can do is define the kinds of objects that will be needed when the program is run: what types of information the program will process and what behavior is associated with each type.

In this chapter, we’ll look at object types, known as classes (luokka), and how to create objects from class definitions. Objects created this way are far more common than singleton objects are.

Our path has a familiar shape: In this chapter, you’ll experiment with classes that have already been defined for you. In future chapters, you’ll learn how to write the program code that defines a class.

Objects as Instances of Classes

The metaphor of filling a form works to clarify the relationship between classes and objects:

So, as programmers, we define:

  • what classes (“types of forms”) our program uses

  • when to create objects of a particular class (“when to make another copy of a form template and fill it in”) — say, whenever the user enters information about a new experience; and

  • how to set the attributes of the new object (“the contents of the filled-in form”).

From now on, we won’t be talking about paper forms. Instead, we’ll adopt these more technical terms:

  • To describe the relationship between an object and its class, we’ll say that the object is an instance (ilmentymä) of the class.

  • A class is a data type (or simply type) for objects (tietotyyppi) .

  • Creating a new instance — a new object — from a class definition is called instantiation (instantiointi).

Using a Class in Scala

We’ll start with a class called Employee. This class describes a type of objects that are similar to the singleton employee object that we created in the previous chapter. The only difference — but it’s a crucial one — is that instead of just one object, we’ll have a data type that represents the general concept of employee. This type enables us to create employee objects with different names and salaries.

If we have access to a class, we can create new instances of it, as shown below for class Employee. You can follow along in the REPL; the code is in module IntroOOP.

Let’s first create one new employee:

Employee("Eugenia Enkeli", 1963, 5500)res0: o1.classes.Employee = o1.classes.Employee@1145e21

We create a new object with a command that starts with the name of the class that we want to instantiate — that is, the type of the object we want to create. In Scala, most class names start with a capital letter.

We pass in constructor parameters (luontiparametri): information that is used for initializing the object. The class Employee is defined so that whenever we create an employee object, we must provide a name, a year of birth, and a salary. The class’s program code uses the parameter values to set the specific characteristics of the new object. (The new employee works full time by default, so its working time is 1.0.)

The instantiation command is an expression. The computer evaluates this expression by reserving some memory for the object’s data and by initializing the object as specified by the class.

The expression’s value is a reference to a new object. In our example, the value is of type Employee.

When the value is an object reference, the REPL’s default behavior is to output an uninformative string. The string’s contents have to do with the way Scala handles objects and are unimportant here. For now, you can think of the string as meaning “a reference to a particular employee object”.

When we create an object, it’s often convenient to also store the resulting reference in a variable. Below, we create another instance of the same class and define a variable that stores a reference to the new instance:

val justHired = Employee("Teija Tonkeli", 1985, 3000)justHired: o1.classes.Employee = o1.classes.Employee@704234

Now we can use the name of the variable to access the object’s methods:

println(justHired.description)Teija Tonkeli (b. 1985), salary 1.0 * 3000.0 e/month

As you just saw, we created two entirely separate employee objects. They have different attributes, but they each have some name, some year of birth, and some monthly salary.

On Instances, Variables, and References

Take a look at a couple of short animations.

That animation already showed that objects are manipulated via references, just as we manipulated buffers via references in Chapter 1.5. The next animation highlights the importance of references even more.

At the beginning of the animation, you’ll be prompted to predict what the program will do. Do that, then watch carefully to see what happens.

References to objects, references to buffers

It’s not coincidental that objects and buffers are accessed similarly via references. Buffers are objects, too. More on that in Chapter 4.1.

Questions on objects, classes, and references

Here are a couple of quick questions on these key concepts, which we’ll need constantly as we go on.

Which of the following are identical for all the instances of a particular class? Consider the options below in the light of this chapter and select all that apply.

Suppose that the following commands have been issued already:

import o1.classes.*
val a = Employee("Eugenia Enkeli", 1965, 5000)
val b = a

Select each correct claim among the following.

The Many Purposes of a Class

Classes as data types

The term “data type” has been cropping up not just in this chapter but before we even mentioned classes. And it’s the same concept that we’re still talking about here. For example, class Employee is a data type for employee objects, just as Int is a data type for integers. Similarly, class Experience is a data type for the experiences in the GoodStuff app.

When we program, we draw on data types from different sources:

  • Some types are specific to the particular application domain that we’re working on. They may be defined by us or other programmers working on the same project. (E.g., Employee, Experience).

  • Some types are an integral part of the programming language. (E.g., Int, String).

  • And some types we adopt from a useful software library. These library classes may have been created by third parties. (E.g., Buffer in Scala’s standard library, Pic in O1’s own library.)

Data types and singleton objects

Even a singleton object has a data type, one that’s unique to just that object. For example, the parrot from Chapter 2.1 has its very own type:

parrotres1: o1.singletons.parrot.type = o1.singletons.package$parrot$@4e0405c9

The parrot is of a data type that no other object shares. This is shown here as parrot.type.

Whenever you define a singleton object, you implicitly define a unique data type for it.

It’s rarely necessary to mention singletons’ data types in program code.

Actually

Even the data type Int is a (somewhat special) class, but we’ll let that lie until Chapter 5.2.

Classes as parts of a conceptual model

In Chapter 2.1, we established that one of the goals of programming is to model problem domains. Classes offer great possibilities for conceptual modeling!

Whereas objects represent individual things in a model (such as specific employees), classes represent general concepts that those things are instances of, such as the generic notion of an employee.

Object-oriented programmers commonly focus on the classes in a program and how those classes combine to describe, in general terms, the possible worlds of objects that come into being as the program runs.

Singleton objects vs. classes

It’s actually fairly rare for a statically typed programming language to let you define singleton objects directly in program code in the Scala manner. In several widely used languages, objects are almost always created by instantiating a class. Which is what we do in Scala, too, for the most part.

That’s not to say that classes are absolutely necessary for OOP. In some languages (with JavaScript the most popular), objects are created by cloning other objects, and one object can serve as a prototype for other similar objects. You can read more about prototype-based programming in Wikipedia.

Classes as abstractions

We’ve already encountered the concept of abstraction in many chapters. And here we’ve come across a new form of abstraction: classes are abstractions — generalizations — of objects. Furthermore, they are abstractions of the domain concepts that they represent.

Classes as parts of program code

In Scala, the code of an object-oriented program consists mainly of the definitions of classes and singleton objects.

It’s possible to define all of a program’s classes in a single file. However, Scala programmers typically group multiple classes in the same file only if those classes are very closely related. In O1, you’ll usually write each class in its own .scala file.

It’s customary to match the name of each code file to the class or singleton object defined therein. The class Employee, for example, is defined in a file named Employee.scala, which we’ll inspect in the next chapter.

On Classes and Their Methods

A class is where we teach objects how to behave.

—Richard Pattis

In the animation of employee objects, did you notice that the objects’ attributes were stored separately from the class? And that methods were associated with the class, not in each object individually?

A class definition is shared among all objects of that type, and there is no need to duplicate it for each object. Each object has a known type (the class) that defines its methods.

By defining the methods of its instances, a class determines how all objects of that type behave. For example, all instances of the Employee class have the methods monthlyCost and description, and each such object responds in the same way when those methods are invoked. As it does so, the object relies on its own instance-specific attributes.

The limitations of objects

You can only command an instance of a class to do something it knows how to do. If you call a method, the method must be defined in the appropriate class, and you must provide parameters as dictated by the class definition. For example, none of the following attempts to command an Employee object succeed:

var example = Employee("Edelweiss Fume", 1965, 5000)res2: o1.classes.Employee = o1.classes.Employee@177e207
example.repairMyGiraffe("pronto!")-- Error:
  |example.repairMyGiraffe("pronto!")
  |^^^^^^^^^^^^^^^^^^^^^^^
  |value repairMyGiraffe is not a member of o1.classes.Employee
example.ageInYear(2014, 2025)-- Error:
  |example.ageInYear(2014, 2025)
  |                  ^^^^^^^^^^
  |                  Found:    (Int, Int)
  |                  Required: Int
example.ageInYear("2025")-- Error:
  |example.ageInYear("2025")
  |                  ^^^^^^
  |                  Found:    ("2025" : String)
  |                  Required: Int

If the method exists but you forget to pass any parameters, there’s no immediate error, but the result is not what you intended and looks funny:

example.ageInYearres3: Int => Int = Lambda$1354/0x0000000801109800@3a5ce4b8

That gobbledygook essentially means that Scala has interpreted the example.ageInYear as referring to a function. However, that function didn’t get called, and the employee’s age wasn’t determined — which wouldn’t have been possible anyway without a parameter value. (The seemingly odd behavior does have a rationale behind it, but we’ll only be able to connect with it once we reach Chapter 6.1.)

Practice Using a Class

The IntroOOP module contains more than the Employee class.

Class PhoneBill represents (again, in a grossly simplified manner) the money that customers owe to a telephone operator. A single instance of the class is a single customer’s phone bill.

Each phone bill is associated with a number of phone calls (zero or more). And each phone call is represented as a single PhoneCall object — that is, an object that is an instance of class PhoneCall.

Practice working with these classes in the REPL as instructed below. Both classes have already been defined in module IntroOOP and are available for you to use in the REPL. In this assignment, you don’t need to define those classes or indeed any other classes.

If you find this assignment hard, you may wish to scroll down to the section titled Pitfalls before returning to work on the assignment.

An introduction to the PhoneCall class

A little bit below, you’ll find a list of specific things to try out in the REPL with class PhoneCall. But first, here’s a quick introduction to the class.

When creating an instance of the class — that is, when creating an object of type PhoneCall — three constructor parameters must be passed to define the properties of the new object. The required parameters are these: an initial fee (for starting the call, in euros), a price per minute (in euros), and a duration in minutes. Each of the three is represented as a Double.

(The basic idea is the same as in the Employee examples above. Each time we created a new Employee instance, we passed in three constructor parameters — a name, a birth year, and a salary. The PhoneCall class also happens to take three parameters, but their types and meanings differ from those required by the Employee class.)

The PhoneCall class specifies that each instance of the class has a parameterless method named totalPrice. This method may be called on any PhoneCall object and returns the total price of the specific phone call that the object represents. To calculate its total price, a PhoneCall object uses the constructor parameters that it received earlier; it also adds a surcharge for using the telecommunications network. (All this is already implemented for you in class PhoneCall, in the IntroOOP module.)

The PhoneCall class also specifies that PhoneCall objects have another method, description, which produces a textual description of the phone call. This method, too, is parameterless.

Do this with PhoneCall:

You’re free to experiment with the class in the REPL, but make sure you do at least the following.

  1. Create a new PhoneCall object — a new instance of the given class. As you do that, also define a val variable that stores a reference to the object. Pick a name for the variable. (For example, callToJenny or anything you like.) Use an initial fee of 0.99 euros, a per-minute price of 0.47 euros, and a duration of 7.5 minutes; those three need to be passed in as constructor parameters.

    If you struggle with the above, see instructions here

    In the REPL, write something like this:

    val callToJenny = PhoneCall(...)
    

    The variable name is yours to pick.

    In the round brackets, write the constructor parameters: the three Double values mentioned above. Separate them with commas.

    By doing that, you have created an instance of the given PhoneCall class. You may then proceed to call methods on the PhoneCall object by making use of the variable that refers to the object (e.g., callToJenny.totalPrice).

  2. Call the totalPrice method of the object you created. Observe that the return value may not be exactly the number you expected: the object automatically adds a surcharge of 13 euro cents, plus 1.3 euro cents per minute.

  3. Call description on your object. (In the description that the method produces, the prices are rounded to fewer decimals. That’s not important now.)

If you wish, go ahead and create other instances of the class, with different numbers as constructor parameters.

An introduction to the PhoneBill class

The following information will soon be useful when you try out the PhoneBill class:

  • When you create a PhoneBill object, use a single constructor parameter: the customer’s name as a string.

  • The method addCall expects a reference to a PhoneCall object as a parameter. When you invoke this effectful method on a particular PhoneBill object, the object will add the given phone call to “itself” (i.e., to the phone bill that the object represents).

  • Phone bills, too, have a totalPrice method that takes no parameters. It returns the sum of all the phone calls that have been previously added to the bill.

  • Another parameterless method on phone bills is named breakdown. It returns a multi-line string that spells out each phone call that has been added to the bill.

Do this with PhoneBill (in combination with PhoneCall):

Go to the REPL and start by doing the following in order, then feel free to experiment as you please.

  1. Create a single phone bill object and a variable that refers to it. Pick any customer name you like (e.g., your own name). Name the variable as you wish (e.g., myBill).

  2. Add a phone call to the new phone bill. More specifically, add the phone call that you created earlier and that a variable refers to (e.g., callToJenny). Use the variable’s name as a parameter expression.

    If you struggle with the above, see here for instructions

    You can create the phone bill object by writing something like this:

    val myBill = PhoneBill("Your Name Here")
    

    The PhoneBill class has been defined to take one constructor parameter, which is a String.

    With that done, you can add a phone call to the bill like so:

    myBill.addCall(callToJenny)
    

    addCall demands one parameter, which is a reference to a PhoneCall object.

  3. Add another phone call to the same phone bill:

    • Use an initial fee of 1.2 euros, a per-minute price of 0.4 euros, and a duration of 30 minutes.

    • You can do this with a single line of code: write the instantiation command directly inside the method-calling command as a parameter. That is, write the PhoneCall bit inside the addCall bit. This way, a reference to the newly created object will be immediately passed as a parameter to the method.

    If you struggle with that single-line part, see here

    You can create a new phone call object and immediately add it to a phone bill as follows. (Just fill in the missing numbers.)

    myBill.addCall(PhoneCall(...))
    

    This creates a new PhoneCall object and a reference that points to it. The reference is then passed as a parameter to addCall without any intermediate step of storing the reference in a separate variable.

  4. Call the phone bill’s totalPrice method (which takes no parameters).

  5. Call the phone bill’s breakdown method (also parameterless) .

  6. Score some points by entering the return values of totalPrice and breakdown in the form below.

The total price reported by the phone bill after both phone calls have been added:

The entire multi-line string that breakdown returns:

An Updated Concept Map

This might be a good time to place the new concepts in our concept map:

Pitfalls

Among the fundamental concepts of object-oriented programming, several pitfalls await the unwary beginner. Many have fallen prey. Watch out.

Although this chapter has aimed to steer you away from common misconceptions, it’s worth underlining a few of them now just to make sure.

Classes are not containers for objects

A fairly common beginner’s mistake is to think of a class as a group of objects, or a storage space for objects. It might seem like objects are somehow located within a class.

However: a class is a description of what a certain kind of objects have in common; it’s not a place where those objects are stored. Or, to extend our earlier metaphor: a class is “a particular form template”, not “a folderful of filled-in forms”.

One practical consequence of this is that you can’t use the name of a class to access, say, a list of the objects that the class “contains”. Nor does a class impose any order or structure on the objects that are supposedly “within it” — becausew, again, they aren’t. When we want to refer to objects, we generally use variables.

It’s certainly common that we want to manipulate several related objects as a group. In such cases, we can place those objects inside a collection such as a buffer. We’ll be doing a lot of that in later chapters.

A class’s instances don’t have “names”

The our employee objects each had a name; the instances of a bank-account class might each have an account number; course objects could each have a course code, and so on. That being so, it’s easy to form a mistaken impression: that the instances of some classes — or even all classes — have a built-in name or identifier that we can use to refer to those instances in program code.

However, none of the above actually work for referring to an instance in program code. If you write the name of an employee to the left of the dot — as in "Edelweiss Fume".ageInYear(2025) — the code works precisely as badly as if you had written the object’s salary or working time there instead. The name, salary, and working time are attributes of the object, but none of them identifies the object among all others, not even if there aren’t any other objects with the attribute value.

To specify which object’s method we intend to call, the usual thing to do is to store a reference to the object in a variable. Then we can access the object through the variable’s name. Which is exactly what we’ve done multiple times in this chapter.

In this respect, the instances of classes differ from singleton objects. The code that defines a singleton object gives the object a name, which serves as an identifier for that individual object. For example, we have referred to singleton objects named account and parrot without defining variables to refer to them.

To be sure, sometimes we want our program to find, from among a collection of objects, the object with particular attribute value, such as a particular name or course code. That’s a separate problem that can be solved in a number of ways. We’ll discuss some of those ways in Chapters 5.5 and 9.2.

A variable that refers to an object is not the object’s name

var example = Employee("Edelweiss Fume", 1965, 5000)

At first glance, this line might look like it creates an object of type Employee and gives it the name example. That interpretation is faulty, however. There’s rather more going on: a variable is defined, an object is created, and a reference that points to the object is stored in the variable.

You’ve already seen that multiple distinct variables can refer to the same object at the same time and that even a single var can refer to different objects at different times during a program run. So we must recognize that a name like example is not the name of an object but of a variable. This is illustrated in many of the ebook’s animations.

Why don’t instances have names?

So, none of an instance’s attributes identifies it, and the name of a variable that refers to the instance isn’t an attribute of the instance. Why do we name variables rather than naming the instances themselves?

Two of the reasons are given below. They may seem a bit abstract now, but that’ll change as you gain more experience with object-oriented programming.

  1. A single object may be referred to in multiple places within a program, and the object may have different “roles” in different contexts. It makes sense to use different names for the object as suits each context. This is much like people’s roles in the real world: the same individual might be referred to as “the president”, “the club’s treasurer”, “my spouse”, “my mother”, “some passer-by”, “the next customer”, “the customer who placed the order”, or “that passenger over there” — depending on who’s doing the referring and from what perspective.

  2. Often, we want to change which object has a particular “role” in a program while it’s running. Yes we still want to use a specific name to access whichever object has that role at the moment. An example is the favorite experience recorded by the GoodStuff application, which may change during a program run. (The real-world comparison fits again: “the president”, “the next customer”, or “my spouse” may refer to different people at different times.)

Review: manipulating instances through variables

The following piece of code defines a couple of vars and a couple of vals and uses the Employee class introduced above.

var first     = Employee("Eugenia Enkeli", 1963, 5500)
val second    = Employee("Teija Tonkeli", 1985, 3000)
val firstVal  = first
var secondVar = second

Which of the following claims are correct? Select all that apply.

More Practice: Pictures as Objects

In Week 1, we created images that had the Pic data type. Pic is actually the name of a class, and each value of type Pic is an object that stores data about what appears in the image.

The Pic type can represent various shapes and other kinds of images. To make it easier for us to work with different kinds of Pics, the o1 package provides a number of functions that create Pics. The functions circle and rectangle are two examples: each creates a new picture object and returns a reference to the created object. We haven’t written Pic(...) to create shape-based images; instead, we have used, and will continue to use, these convenient functions.

Up to now, the only thing we’ve done with a Pic is pass it to the show function, which displays the image. However, that’s not nearly all we can do: these picture objects come with a wealth of methods. We’ll now take a look at a few of them.

The basic attributes of a Pic

We can query a Pic object for its width and height.

val littleCircle = circle(100, Red)littleCircle: Pic = circle-shape
littleCircle.widthres4: Double = 100.0
littleCircle.heightres5: Double = 100.0
val bigger = circle(littleCircle.width * 3, Red)bigger: Pic = circle-shape
bigger.widthres6: Double = 300.0

The same functionality is available on any Pic object, including those loaded from a file:

val loaded = Pic("ladybug.png")loaded: Pic = ladybug.png
println("The pic has a total of " + loaded.width * loaded.height + " pixels.")The pic has a total of 900.0 pixels.

“Editing” a Pic

An image can be rotated with a single method call. Try this and see for yourself.

val myRectangle = rectangle(100, 200, Orange)myRectangle: Pic = rectangle-shape
val rotated = myRectangle.clockwise(45)rotated: Pic = rectangle-shape (transformed)
show(rotated)show(myRectangle)

The clockwise method takes a rotation angle in degrees.

It returns a new image object that is a rotated version of the original. The REPL reports this as shown.

Neither clockwise nor any of a Pic’s other methods has an effect on an existing image. The original Pic remains what it was.

The history method returns information about how a Pic has been formed:

rotated.historyres7: List[String] = List(clockwise, rectangle)

We get a report showing that the picture has been formed by making a rectangle and rotating it.

For each piece of code, pick the option that correctly describes what the code does.

(This first code fragment uses the counterclockwise method, which works like clockwise but in reverse.)

var horsePic = Pic("horse.png")
horsePic.counterclockwise(35)
show(horsePic)
var horsePic = Pic("horse.png")
horsePic = horsePic.clockwise(15)
horsePic.clockwise(60)
show(horsePic)

First we do this:

var horsePic = Pic("horse.png")
horsePic = horsePic.clockwise(15)

And then this:

horsePic = horsePic.clockwise(60)
show(horsePic)
val horsePic = Pic("horse.png")
val rotatedSlightly = horsePic.clockwise(15)
val rotatedMore = rotatedSlightly.clockwise(60)
show(rotatedMore)

Pictures also have the following methods. The lot of them are effect-free.

  • scaleBy returns a new image that is a scaled version of the original. This method expects a single Double parameter: a scaling factor. A factor between zero and one makes a smaller image, a factor above one a larger image.

  • flipVertical and flipHorizontal return vertical and horizontal mirror images, respectively. These methods take no parameters.

Experiment with these methods freely. Do at least the following in the REPL:

  1. Choose a Pic to work with. It can be a basic shape or a loaded image, whichever you prefer.

  2. Invoke scaleBy on the image to enlarge the image by a factor of 2.

  3. Turn the enlarged image upside down with flipVertical.

  4. Rotate the flipped image 15 degrees clockwise.

  5. Call the history method on the image you got. In the field below, paste the REPL’s description of what history returns.

Combining Pics

Let’s create a new picture by placing two existing pictures side by side:

val first = rectangle(50, 100, Red)first: Pic = rectangle-shape
val second = rectangle(150, 100, Blue)second: Pic = rectangle-shape
val combination = first.leftOf(second)combination: Pic = combined pic
show(combination)

The methods rightOf, above, and below work like leftOf does in the above REPL session. Try them!

As another example, this code forms an image by arranging four copies of the same image in a two-by-two layout:

val testPic = Pic("https://en.wikipedia.org/static/images/project-logos/enwiki.png")testPic: Pic = https://en.wikipedia.org/static/images/project-logos/enwiki.png
val sideBySide = testPic.leftOf(testPic)val sideBySide: Pic = combined pic
show(sideBySide.above(sideBySide))

The virtual “ping-pong table” you saw in Chapter 1.2’s Pong game was constructed similarly, by placing shapes together.

Let’s look at some more code. Here’s one example, for starters:

// Example 1
val first = rectangle(50, 100, Red)
val second = rectangle(150, 100, Blue)
val combination = first.leftOf(second)
val rotated = combination.clockwise(30)
show(rotated)

And here are five more:

// Example 2
val first = rectangle(50, 100, Red)
val second = rectangle(150, 100, Blue)
val combination = first.leftOf(second)
show(combination.clockwise(30))
// Example 3
val first = rectangle(50, 100, Red)
val combination = first.leftOf(rectangle(150, 100, Blue))
show(combination.clockwise(30))
// Example 4
val second = rectangle(150, 100, Blue)
val combination = rectangle(50, 100, Red).leftOf(second)
show(combination.clockwise(30))
// Example 5
val combination = rectangle(50, 100, Red).leftOf(rectangle(150, 100, Blue))
show(combination.clockwise(30))
// Example 6
show(rectangle(50, 100, Red).leftOf(rectangle(150, 100, Blue)).clockwise(30))

How many of Examples 2 to 6 produce the same picture as Example 1 does? Please answer with a single digit between 0 and 5.

In the field below, enter an expression of type Pic that calls either above or below to produce an image where a rectangle appears below a circle.

Take care of the whole thing with a one-liner in the vein of Example 6, above; don’t use any variables. You may assume that o1.* has been imported. Feel free to try the show command in the REPL, but please don’t include that command as part of your answer.

Pick any size and color for the two shapes. Maybe it’s a happy little tree, or whatever your heart desires.

In Week 1, you wrote a function verticalBar that returned a value of type Pic. In the following exercises, too, you’ll write functions that return references to Pic objects. The first and easiest of these exercises is voluntary.

A practice problem

Write an effect-free function called twoByTwo that:

  • takes an image as its only parameter; and

  • returns (but does not show!) an image in which the given image appears four times in a two-by-two layout.

This function is an abstraction of one of the examples above: it does the same thing but works for an arbitrary image that you pass in as a parameter.

Write the function in the Subprograms module, in week2.scala. (Note the file name!)

A+ presents the exercise submission form here.

Flag #1: Somalia

../_images/flag_of_somalia.png

Write an effect-free function flagOfSomalia that:

  • takes the entire flag’s width (a Double) as its only parameter; and

  • returns a Pic of the national flag of Somalia, whose:

    • width is determined by the parameter;

    • height is two thirds of the width;

    • star is four thirteenths of the total flag width; and

    • colors are RoyalBlue and White.

Place the function in the Subprograms module, in week2.scala. (Note the file name!)

You can use the star function in package o1 to create a five-pointed star. Pass the star’s width and color as parameters.

Position the star with onto. Here’s a separate example of that method:

val darkCircle = circle(300, Black)darkCircle: Pic = circle-shape
show(testPic.onto(darkCircle))

Watch out with integer division when you do the math. Remember this (from Chapter 1.3):

2 / 3res8: Int = 0
2 * 150.0 / 3res9: Double = 100.0

Your function should return the picture, not display it with show. If you want to show the result, you can combine the two functions like this:

show(flagOfSomalia(400))

A+ presents the exercise submission form here.

Flag #2: Finland

../_images/suomen_lippu.png

In the same file, write a function flagOfFinland that:

  • receives a total width as a Double just like the previous function; and

  • returns an image of the Finnish flag with the given width, using the official proportions as per the illustration on the right.

Use the colors White and Blue.

Instructions and hints:

  • First compute the length denoted by x in the illustration. Use that as a basic unit for computing the other measurements. Create rectangles of the right sizes to serve as the flag’s “pieces”. Position the pieces side by side or one above the other. There’s no need to use a scaling method.

  • Please note that the function’s parameter specifies the width of the entire flag, not the length marked as x in the illustration.

  • Use local variables.

  • Remember: Pic objects are immutable. Their methods generate new Pic objects; they don’t mutate the original Pic. (See, for example, the rotating-horse example above.)

  • Don’t make your function show the flag. Just return the picture.

  • See if you can solve the assignment both with the help of onto and without.

If you see “thin white gaps” in your flag, look here

Depending on your chosen solution, you might see thin “gaps” of white between some of the flag’s blue areas, even though you programmed no such thing. If that happens, just ignore the gaps. They are caused by a limitation in the graphics library that we rely on; that issue will be resolved later. Submit your work as if the gaps weren’t there.

A+ presents the exercise submission form here.

Go ahead and make other images as well.

Summary of Key Points

  • In addition to defining singleton objects, you can define classes. A class represents the shared features (variables and methods) of objects that resemble each other.

  • Classes make object-oriented programming more convenient. Class definitions tend to play a larger part in object-oriented programs than singleton objects do.

  • Given a class definition, you can instantiate the class. That is, you can create an instance of the class, an object. The instance has the features (variables and methods) defined by its class, but it has its own instance-specific values stored in the variables.

  • In Scala, you can instantiate a class with an expression of the form ClassName(constructorParams). Constructor parameters convey information needed to initialize the new object.

  • Adopting different perspectives, you can view classes as: 1) parts of program code; 2) data types; 3) descriptions of generic concepts in a conceptual model; and 4) useful abstractions.

  • Objects are manipulated via references.

    • You can assign an object reference to a variable and then access the object via the variable.

    • Multiple references may point to the same object from different parts of the program.

    • A single (var) variable may store a reference to one object at one time and a reference to another object at another time.

  • Links to the glossary: class, data type; instance, to instantiate, constructor parameter; reference; abstraction.

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