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 5.2: Objects Everywhere
About This Page
Questions Answered: What can I do with a String
object? How do
I parse string data — a star catalog, say? So, String
s are
objects — what else is an object?
Topics: Several earlier topics are revisited from an object-oriented perspective. Secondary topics include operator notation, and package objects.
What Will I Do? Read and work on short assignments.
Rough Estimate of Workload:? Two or three hours?
Points Available: A60.
Related Modules: Miscellaneous, Stars.
Notes: This chapter makes occasional use of sound, so speakers or headphones are recommended. They aren’t strictly necessary, though.
Introduction
In Chapter 2.1, we undertook to learn object-oriented programming. Since then, you’ve
created many classes and objects and used classes and objects created by others. Along
the way, it has turned out that many of the basic building blocks that we use in Scala
are objects and have useful methods; these objects include vectors and buffers (Chapter 4.2)
and Option
s (Chapter 4.3).
Scala is a notably pure object-oriented language in that all values are objects and the operations associated with those values are methods.
How is that? After all, we’ve also used “standalone” functions such as max
and min
and written such functions ourselves. They weren’t methods of any object, right? And we
have Int
s and Boolean
s and the like, which work with operators rather than methods,
right?
We’ll see.
Numbers as Objects
The data type Int
represents integer values. This type, too, is in fact a class and
individual integers are Int
objects, even though we haven’t so far viewed them from
that perspective in O1.
It’s true that Int
isn’t quite a regular class. One unusual thing about this type is
that you don’t need to instantiate it with new
; instead, you get new values of type
Int
from literals. The Int
class is defined as part of the core package scala
,
which makes it an inseparable part of the language itself. Some of the classes in this
package, Int
being one of them, are associated with “unusual” features such as a
literal notation.
That being said, Int
is a perfectly normal class in that it defines a number of
methods that you can invoke on any Int
object.
val number = 17number: Int = 17 number.toBinaryStringres0: String = 10001
What we did there is call toBinaryString
, a method that returns a description of the
integer in the binary (base 2) number system. The base-ten integer 17 happens to be 10001
in binary format.
You can even invoke a method on an integer literal:
123.toBinaryStringres1: String = 1111011
The to
method
Int
objects have a method called to
, which returns an object that represents all the
numbers between the target object (2
in the example below) and the parameter object (6
).
val numbersFromTwoToSix = 2.to(6)numbersFromTwoToSix: Range.Inclusive = Range 2 to 6 numbersFromTwoToSix.toVectorres2: Vector[Int] = Vector(2, 3, 4, 5, 6)
Range
in our example contains the numbers 2, 3,
4, 5, and 6 in ascending order, which is easier to
see by copying the range’s contents into a vector.
(The vector takes up more space in the computer’s memory
than the Range
, because it stores each element separately
whereas a Range
stores the first and last element only.)We’ll return to to
a bit further down. And we’ll put it to good use in Chapter 5.6.
The toDouble
method
Here is an example of toDouble
, a frequently used method on Int
objects. It
returns the Double
that corresponds to the Int
.
val dividend = 100dividend: Int = 100 val divisor = 7divisor: Int = 7 val integerResult = dividend / divisorintegerResult: Int = 14 val morePrecisely = dividend.toDouble / divisormorePrecisely: Double = 14.285714285714286 val resultAsDouble = (dividend / divisor).toDoubleresultAsDouble: Double = 14.0
As you know, we don’t strictly need toDouble
to do all that: we could have written
1.0 * dividend / divisor
, for example. But toDouble
says straight out what we’re
after.
abs
, min
, etc.
Int
objects also have methods for many familiar mathematical operations. For example,
we can obtain a number’s absolute value or select the smaller of two integers as shown
below.
-100.absres3: Int = 100 val number = 17number: Int = 17 number.min(100)res4: Int = 17
Index numbering
Wikipedia has an article on Zero-based numbering.
Should indices start at 0 or 1?My compromise of 0.5 was rejected without, I thought, proper consideration.
Operators as Methods
So, numbers are objects and have methods. How do operators fit in?
Look at the documentation of class Int
in the Scala API:
Browsing the documentation, you’ll find the methods introduced above: toDouble
, to
,
abs
, and so forth. In the same list, you’ll find methods whose symbolic names look
familiar: !=
, *
, +
, etc. (You don’t need to otherwise understand the document at
this point.)
Let’s try something:
val number = 10number: Int = 10 number.+(5)res5: Int = 15 (1).+(1)res6: Int = 2 10.!=(number)res7: Boolean = false
Int
. We can tell an Int
object to execute these methods as
illustrated. The output is familiar; it’s just that now we used
this somewhat more awkward notation to produce it.Does that mean that we have an operator +
, as in number + another
, and, separately,
a method +
, as in number.+(another)
?
No. What we have is a single method and two ways to write the method call. As it turns
out, Scala permits us to leave out the dot and the brackets when calling a method that
takes exactly one parameter. The expressions number + another
and number.+(another)
mean precisely the same thing. Operators are actually methods.
The concept of operator doesn’t even officially exist in Scala. (In many other languages, it does.) But it can be convenient to think of certain Scala methods — such as the arithmetic methods on numbers — as operators and omit the extra punctuation when we invoke them.
Dot notation vs. operator notation
You saw that we can omit the dot and the brackets when calling methods such as +
.
The same also goes for other single-parameter methods. For example, our Category
class could contain either one of these expressions:
newExperience.chooseBetter(this.fave)
newExperience chooseBetter this.fave
The former is known as dot notation (pistenotaatio) and the latter as operator notation or infix operator notation (operaattorinotaatio).
Each notation has its benefits. Choosing between them is in part a matter of taste.
In this ebook, we use the operator notation sparingly. This is because dot notation works consistently for all parameter counts and because it highlights the target object and parameter expressions better, which is nice for learning.
We’ll mostly use operator notation for arithmetic, relational, and logic operators; we’ll
also use it in a handful of other places where it’s particularly natural. For example, most
of us would probably agree that it’s nicer to call to
on an Int
by writing 1 to 10
rather than 1.to(10)
.
In your own programs, you’re free to adopt either notation. We recommend that beginners, especially, follow the ebook’s convention. In any case, you should be aware of both notations so that you can read Scala programs written by others. Outside O1, the operator notation is somewhat more common than it is in this ebook.
“Operators” on Booleans and buffers
Logical operators (Chapter 5.1) are methods on Boolean
objects.
The buffer “operators” +=
and -=
are methods, too. You can use either operator or
dot notation to invoke them. The former is probably preferred by all and sundry.
val myBuffer = Buffer("first", "second", "third")res8: Buffer[String] = ArrayBuffer(first, second, third) myBuffer += "fourth"myBuffer.+=("fifth")
GoodStuff revisited
In earlier chapters, you’ve seen interactive animations where GoodStuff’s objects communicate with each other via messages. In those diagrams, buffers or integers didn’t feature as objects.
As is clear by now, they too are objects: for example, an
Experience
object calls the >
method on an Int
object to
find out which of two integers is bigger (so as to find out which
experience is better). Communication between objects thus plays
an even greater part in the program than previously discussed.
String
Objects and Their Methods
At this point, it barely registers as news that String
is a class, string values are
its instances, and string operators are its methods.
"cat".+("fish")res9: String = catfish
A vector contains elements of some type, each at its own index. A string contains
characters each at its own index. There is an obvious analogy between the two concepts,
and it makes sense that strings have many of the same methods that vectors and buffers
do. Strings also come with additional methods that are specifically designed for
working on character data. The examples and mini-assignments below will introduce to
some of the methods defined on Scala’s String
objects.
String manipulation is very common across different sorts of programs. You’ll find the methods useful in many future chapters, and in this one, too. Once again, the point is not that you memorize the details of every method; with practice, you’ll learn to remember the common ones. The important thing is for you to get an overview of what sorts of tools are available to you.
The length
of a string
The length
method returns the number of characters in a string.
"llama".lengthres10: Int = 5
Getting a number from a string: toInt
and toDouble
Let’s experiment with user inputs and String
methods. We’ll write a tiny program that
prompts the user for a number, then reports how many digits the number has as well as
the result of another computation. The program should work like this in the text console:
Please enter an integer: 2021 Your number is 4 digits long. Multiplying it by its length gives 8084.
Here’s a first draft of the code:
import io.StdIn._
object InputTest extends App {
val input = readLine("Please enter an integer: ")
val digits = input.length
println(s"Your number is $digits digits long.")
val multiplied = input * digits
println(s"Multiplying it by its length gives $multiplied.")
}
The above implementation “works” as shown in the two example runs below:
Please enter an integer: 2021 Your number is 4 digits long. Multiplying it by its length gives 2021202120212021.
Please enter an integer: laama Your number is 5 digits long. Multiplying it by its length gives laamalaamalaamalaamalaama.
We processed the input as a String
, which is indeed necessary if we want to call its
length
method. However, multiplying a string with a number doesn’t give us what we
want. It’s also displeasing that our program claims to treat any old string as if it
was a number.
In this case, just like on countless different real-world programs, we’ll need to
interpret a string that contains numerical characters as a number. For instance,
given a string that consists of the characters '1'
,`'0', and `'5'
, we’d like to
produce the Int
value 105
.
A straightforward solution is to call toInt
(or toDouble
, where appropriate):
val digitsInText = "105"digitsInText: String = 105 digitsInText.toIntres11: Int = 105 digitsInText.toDoubleres12: Double = 105.0
Let’s apply that to our program:
import io.StdIn._
object InputTest extends App {
val input = readLine("Please enter an integer: ")
val digits = input.length
println(s"Your number is $digits digits long.")
val multiplied = input.toInt * digits
println(s"Multiplying it by its length gives $multiplied.")
}
The only change to the previous code is that now we interpret the numbers as an Int
for the multiplication to work.
A safer choice: toIntOption
and toDoubleOption
The previous program suffered from the common problem that invalid user input can lead to missing information. Although some small programs for personal use don’t always need to cover such exceptional situations, many other programs do.
You already know that the Option
type is one way to represent missing information. One
easy way to produce an Option
is to call toIntOption
rather than toInt
; there is
also toDoubleOption
and some other similar methods.
"100".toIntOptionres13: Option[Int] = Some(100) "a hundred".toIntOptionres14: Option[Int] = None "100.99".toDoubleOptionres15: Option[Double] = Some(100.99)
Mini-assignment: toIntOption
Take out the InputTest
program o1.inputs
in the Miscellaneous module.
Merely replacing the toInt
call with toIntOption
won’t make the code work.
On the contrary, that simple modification makes the code unrunnable. (Why?)
You’ll need to deal separately with the case where the input is valid and the
case where it’s invalid. Edit the program so that it works as shown in the two
example runs below. (Hint: use match
to cover each case.)
Please enter an integer: 105 Your number is 3 digits long. Multiplying it by its length gives 315.
Please enter an integer: ten That is not a valid input. Sorry!
A+ presents the exercise submission form here.
Removing whitespace: trim
The trim
method is also handy when you need to process data that you’ve obtained
from an external source. It returns a string in which space characters and other
*whitespace* have been removed from both ends.
var text = " hello there "text: String = " hello there " println("The text is: " + text + ".")The text is: hello there . println("The text is: " + text.trim + ".")The text is: hello there.
If there is no leading or trailing whitespace, trim
returns an untouched string:
text = "poodle"text: String = poodle println("The text is: " + text.trim + ".")The text is: poodle.
trim
can be summarized as “removing empty characters from a string”. To be more
specific, however, the method does not actually change the original string but
produces a new string that contains the same characters as the original except for the
omitted whitespace. All methods on String
objects are effect-free, trim
included.
Every String
object is immutable.
Picking out a character: apply
and lift
The apply
method takes an index as a parameter and retrieves the corresponding character
from the string. The numbering starts at zero, so the following code checks the fourth and
fifth letters in "llama":
"llama".apply(3)res16: Char = m "llama".apply(4)res17: Char = a
Char
class. A String
is a sequence of
zero or more characters; a Char
is exactly one character.There’s also a more succinct way to access a character at an index. Try "llama"(3)
, for
instance.
And there is a safer way: the lift
method you know from other collections.
"llama".lift(3)res18: Option[Char] = Some(m) "llama".lift(4) res12: Option[Char] = Some(a) "llama".lift(123)res19: Option[Char] = None
split
ting a string
The split
method divides a string into parts at the given separator. In this example,
the separator is a space:
val quotation = "A class is where we teach objects how to behave. ---Richard Pattis"quotation: String = A class is where we teach objects how to behave. ---Richard Pattis val words = quotation.split(" ")words: Array[String] = Array(A, class, is, where, we, teach, objects, how, to, behave., ---Richard, Pattis) words(1)res20: String = class
split
returns an Array
, a collection of elements similar to
a vector or a buffer.Any string can serve as a separator:
quotation.split("ch")res21: Array[String] = Array(A class is where we tea, " objects how to behave. ---Ri", ard Pattis)
Letter size: toUpperCase
and toLowerCase
val sevenNationArmy = "[29]<<e--------e--g--. e--. d--. c-----------<h----------->e--------e--g--. e--. d--. c---d---c---<h-----------/360"sevenNationArmy: String = [29]<<e--------e--g--. e--. d--. c-----------<h----------->e--------e--g--. e--. d--. c--- d---c---<h-----------/360 play(sevenNationArmy)
That needs to be played louder.
The function play
plays upper-case notes louder than lower-case ones. There is an
easy way to produce upper-case letters:
play(sevenNationArmy.toUpperCase)
Here’s an additional example of the method and its sibling toLowerCase
:
"Little BIG Man".toUpperCaseres22: String = LITTLE BIG MAN "Little BIG Man".toLowerCaseres23: String = little big man
Comparing strings: compareTo
Searching for characters: indexOf
and contains
The methods indexOf
and contains
work much like the corresponding methods on vectors
and buffers (from Chapter 4.2). You can use them to find out if a given substring occurs
within a longer string:
"fragrant".contains("grant")res24: Boolean = true "fragrant".contains("llama")res25: Boolean = false "fragrant".indexOf("grant")res26: Int = 3 "fragrant".indexOf("llama")res27: Int = -1
take
, drop
, and company
take
, drop
, head
, and tail
are available on strings, too. These methods and
their variants are analogous to the collection methods from Chapter 4.2. Here are a few
examples:
val fromTheLeft = "fragrant".take(4)fromTheLeft: String = frag val fromTheRight = "fragrant".drop(5)fromTheRight: String = ant "fragrant".take(0)res28: String = "" "fragrant".take(100)res29: String = fragrant "fragrant".takeRight(5)res30: String = grant "fragrant".dropRight(5)res31: String = fra
val first = "fragrant".headfirst: Char = f val rest = "fragrant".tailrest: String = ragrant val firstIfItExists = "fragrant".headOptionfirstIfItExists: Option[Char] = Some(f) val firstOfEmptyString = "fragrant".drop(10).headOptionfirstOfEmptyString: Option[Char] = None
Optional assignment: string insertion
In misc.scala
of module Miscellaneous, write an insert
function that adds a
string into a specified location within a target string. It should work as illustrated
below.
val target = "viking"target: String = viking" insert("olinma", target, 2)res32: String = violinmaking insert("!!!", target, 0)res33: String = !!!viking insert("!!!", target, 100)res34: String = viking!!! insert("!!!", target, -2)res35: String = !!!viking
A+ presents the exercise submission form here.
Speaking of strings: special characters
Chapter 4.2 mentioned in passing that you can include a newline character (a line break)
in a string by writing \n
. There’s a similar notation for a number of other special
characters. Here are a few of them:
Character | Written in literals as |
---|---|
newline | \n |
tabulator | \t |
double quotation mark | \" |
backslash | \\ |
For example:
println("When I beheld him in the " +
"desert vast,\n\"Have pity " +
"on me,\" unto him I cried,\n" +
"\"Whiche'er thou art, " +
"shade or real man!\"")
That produces the following output:
When I beheld him in the desert vast,
"Have pity on me," unto him I cried,
"Whiche'er thou art, shade or real man!"
As you can see, the line breaks appear where the newline characters are.
An alternative is to mark the string’s beginning and end with three double-quote characters. This tells Scala to interpret everything in between, special characters and all, as individual characters, so that there’s no need to use the backslash as an “escape”. The following code produces the same output as the one above.
println("""When I beheld him in the desert vast,
"Have pity on me," unto him I cried,
"Whiche'er thou art, shade or real man!"""")
Assignment: tempo
Introduction
The example below uses a function that takes in similar string of music as play
does
and returns the tempo of that music as an integer.
tempo("cccedddfeeddc---/180")res36: Int = 180 tempo(s"""[72]${" "*96}d${"-"*39}e---f---d${"-"*39}e---f---d${"-"*15}e---f---d${"-"*15}e---f--- f#-----------g${"-"*17}&[62]${" "*104}(>c<afd)--(>c<afd)--------(afdc)--(>c<afd)-------(afdc) ${"-"*17} (>c<abfd)--(>c<abfd)--------(abfdc)--(>c<abfd)-------(abfdc)${"-"*17} (hbgfd) ${"-"*17}(hbg>fd<)${"-"*22}(>ce<hbg)---- (>d<hbge)---------- (>db<hbge)---------- (>c<hbg#e)----- &[29]${" "*96}<<<${"c-----------"*11}cb-----------<${"hb-----------"*3}hb-----&P:a----------- ${"a--------a--a-----------"*11}/480""")res37: Int = 480
The function completely ignores the string’s actual musical content; it just extracts the tempo from the end. If there’s no tempo recorded in the string, the function returns the integer 120:
tempo("cccedddfeeddc---")res38: Int = 120
Task description
Implement tempo
in misc.scala
of module Miscellaneous.
Instructions and hints
- The function must return an
Int
, not aString
that contains the digits. The function needs to divide the string, select one of the parts, and interpret it as an integer. - There are a great many ways to implement the function. Just the
String
methods introduced in this chapter suggest multiple solutions. Can you find more than one? - You may assume that the given string has at most a single slash
character
/
, no more. You may also assume that in case there is a slash in the string, it’s followed by one or more digits and nothing else. In other words, you don’t have to worry about invalid inputs.
A+ presents the exercise submission form here.
A better representation for songs?
In this ebook, you’ve seen some fairly long strings being passed
as parameters to play
. Writing and editing strings such as these
is laborious and attracts bugs. Fortunately, you don’t have to.
If you want, you can reflect on what functions you might create to help you notate songs in O1’s string format.
You may also: 1) consider what other format we could use to represent musical notes in a program; 2) envision an application where an end user can easily write and save songs; and 3) find out what solutions are already out there.
Assignment: Star Maps (Part 3 of 4: Star Catalogues)
We concluded Chapter 4.4’s Stars assignment with this observation:
We can now display individual stars in a star map, but it would take a lot of manual work to display a map with more than a sprinkling of stars. It would be much more convenient if we could load star data into our program from a repository of some sort.
The folders test
and northern
of the Stars module each contain files named stars.csv
.
Take a look.
- The file under
test
contains a semicolon-delimited description of a few imaginary stars. You’ll learn what each number means in just a moment. - The file under
northern
contains a much longer list of actual stars (originally from the VizieR service).
For now, feel free to ignore the other files under northern
.
We’ll now improve our program so that it 1) reads in the star data from the files
as strings, 2) creates Star
objects to represent the information encoded in those
strings, and 3) displays all the stars as graphics. The last of the three subproblems
you already solved in Chapter 4.4. The second subproblem of interpreting the string
data you’ll solve now. The first subproblem of actually handling the file has been
solved for you in the given code. (More on files later in Chapter 11.3.)
Task description
Run StarryApp
. Be disappointed by its flat black output. Exit the app.
Actually, the app is very close to working nicely, but a crucial method of the SkyFiles
singleton object is missing. That method, parseStarInfo
, does what was just discussed:
takes in a string that describes a star (as the lines of stars.csv
do) and creates a
corresponding Star
-object.
Read the documentation for parseStarInfo
. It explains what each part of the input
string is expected to contain and which of those parts are significant for your task.
Then implement the method.
Instructions and hints
- The method has been documented as part of the
SkyFiles
object in packageo1.stars.io
. - The folder that the app loads its star data from is named at the
top of
StarryApp
. As given, the app uses thetest
folder, which is a good choice until you feel confident that your method works. Later, you can change the folder tonorthern
to produce a more impressive star map. - You do not have to consider what happens if the input string
doesn’t consist of six or seven semicolon-separated parts or
otherwise fails to adhere to the specification. Assume that the
given data string is valid.
- The program will crash with a runtime
error if the selected
stars.csv
contains invalid data. This would obviously be unacceptable in a proper real-world application but it will do for present purposes.
- The program will crash with a runtime
error if the selected
- You can find helpful methods in this very chapter.
- Some of the star data (the z coordinate and one of the ID numbers) are irrelevant to this assignment.
A+ presents the exercise submission form here.
Bonus Material: Package Objects and import
Statements
The remaining topics in this chapter aren’t particularly important for O1 or indeed for learning to program in general, but they can be nice to know for programming in Scala.
Objects as packages
What about the “separate functions” that you have used and written,
such as sqrt
(Chapter 1.6) or toMeters
(Chapter 1.7) or tempo
(just now)? Those functions weren’t attached to any object, so do
they count as object-oriented programming?
In a way, yes.
Technically, even those functions were methods, even though it didn’t seem like it. This fact has its basis in two things.
One: in Scala, we can import methods from an object. For
example, import mysingleton._
imports all the methods of the
mysingleton
object. That means that we can then omit the dot
and the object’s name as we call the method, and our method
calls won’t look like method calls.
Two: we can define a singleton object that is meant to be
import
ed from and that contains an assortment of methods that
are more or less related to each other. Such an object is termed a
package object (pakkausolio).
In this chapter, for example, you defined some functions in an object defined as follows.
package o1
object misc {
// Your code went here.
}
What you did, then, was define the functions as methods of a
package object named misc
. After importing those methods with
import o1.misc._
(which our REPL does automatically), you
could invoke them as if no target object was involved at all.
The Scala API features various package objects. One of them is
math
, which defines sqrt
and other familiar functions.
So, in a technical sense, these functions, too, are methods on singleton objects. This is consistent with Scala’s pure object-oriented design.
However, in practice, we commonly don’t think about the methods on package objects as being methods. In a sense, that’s exactly what a package object is: an object whose object-ness we can “overlook”.
One of the changes in version 3 of Scala, which has just come out in 2020 (but we don’t use yet in O1), is that package objects are superseded by a different solution.
On package objects and object-orientation
If we were to design an entire piece of software so that we use only package objects and their methods, our design won’t capture the spirit of object-oriented programming. When using Scala in an object-oriented fashion, it’s customary to make limited use of methods in package objects.
For example, the package object scala.math
is more the exception
than the rule; most of the Scala API relies on classes that we
instantiate. And indeed even many of that package object’s contents
are also available as methods on instances: for example, you can
get the absolute value of a number either “function-style” with
abs(a)
or “object-style” with a.abs
. Take your pick.
println
, readLine
, and package objects
The familiar println
function is actually a method on a singleton
object named Predef
. This one particular object has an elevated
status in Scala: its methods can be called in any Scala program
without an import
and without an explicit reference to the object.
As for readLine
, you learned to use it in Chapter 2.7 by first
import
ing scala.io.StdIn._
. As you did that, you essentially
used a singleton object StdIn
as a package object.
import
ing just about anywhere
In the ebook’s examples, we have generally placed any import
statements at the top of the Scala file. This is a common practice.
However, Scala also lets you import
locally within a particular
class or object or even an individual method.
import myPackage1._
class X {
import myPackage2._
def myMethodA = {
// Here, you can use myPackage1 and myPackage2.
}
def myMethodB = {
import myPackage3._
// Here, you can use myPackage1, myPackage2, and myPackage3.
}
}
class Y {
// Here, you can use only myPackage1.
}
Such local import
s sometimes make code easier to read.
import
ing from an instance
As noted above, you can use a singleton object like a package and
import
its methods. As a matter of fact, you can even do the same to
an instance of a class, if you’re so minded.
class Human(val name: String) { val isMortal = true def greeting = "Hi, I'm " + this.name }defined class Human val soccy = new Human("Socrates")soccy: Human = Human@1bd0b5e import soccy._import soccy._ greetingres39: String = Hi, I'm Socrates
The last command issued above is actually a shorthand for soccy.greeting
.
However, in most cases import
ing from instances like this is liable
to make your code harder to read.
Bonus Material: Rich vs. Thin Interfaces
Many similar methods — good or bad?
Here are a few student remarks on many methods on strings and collections that have come up in this and earlier chapters:
Indeed it is not rare that a class has several methods that do similar things and that aren’t all strictly necessary. This is the case with many of Scala’s core classes, too.
In other words, these classes don’t offer their users a thin interface but a rich one. This theme is discussed in Programming in Scala (which is one of the books listed on our resources page):
Thin versus rich interfaces represents a commonly faced trade-off in object-oriented design. The trade-off is between the implementers and the clients of an interface. A rich interface has many methods, which make it convenient for the caller. Clients can pick a method that exactly matches the functionality they need. A thin interface, on the other hand, has fewer methods, and thus is easier on the implementers. Clients calling into a thin interface, however, have to write more code. Given the smaller selection of methods to call, they may have to choose a less than perfect match for their needs and write extra code to use it.
It’s true that the rich interface’s user may initially feel a bit overwhelmed with the wealth of options at their disposal, but usually the riches pay off sooner rather than later.
Summary of Key Points
- As an object-oriented language, Scala is pure: all values are objects.
Int
,Double
, andString
, for example, are classes and values of those types are objects. These objects have a variety of frequently useful methods.- The so-called operators are actually methods. In Scala, there are two ways to call a method: dot notation and operator notation.
- Links to the glossary: object-oriented programming; dot notation, operator notation; package object.
Does that go for other languages, too?
Scala is certainly not the only language where “everything is an object”. However, it does differ in this respect from some other object-oriented languages in common use. In the Java programming language, for example, objects and classes are used for a variety of data, but some fundamental data such integers and Booleans are instead represented as so-called primitive types. Scala’s all-encompassing object system eliminates special cases and gives the language a more consistent feel.
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 Stars program is an adaptation of a programming assignment by Karen Reid. It uses star data from VizieR.
This chapter does injustice to music by Glenn Miller and The White Stripes. Thank you and sorry.
The guy in the desert vast was Dante Alighieri.
Range
. A range is an collection of numbers; like a vector, it is immutable.