Scala provides mechanisms to make method calls look like regular function calls. It uses special case apply
and update
methods to allow a kind of shorthand call notation that can reduce the clutter of your code.
apply
MethodThe apply
method provides a shorthand way of calling a method on a class. So, as we saw, you can use them as factory-style creation methods, where given some class such as our old friend Customer
:
class
Customer
(
name
:
String
,
address
:
String
)
…you can add an apply
method to its companion object.
object
Customer
{
def
apply
(
name
:
String
,
address
:
String
)
=
new
Customer
(
name
,
address
)
}
You can then either call the method directly, or drop the apply
part, at which point Scala will look for an apply method that matches your argument list and call it.
Customer
.
apply
(
"Bob Fossil"
,
"1 London Road"
)
Customer
(
"Rob Randal"
,
"14 The Arches"
)
You can also have multiple apply methods. For example, we could create another method to default the address field of our customer.
def
apply
(
name
:
String
)
=
new
Customer
(
name
,
"No known address"
)
Customer
(
"Cuthbert Colbert"
)
You don’t have to use apply
methods as factory methods though. Most people end up using them to make their class APIs more succinct. This is key to how Scala can help make APIs more expressive. If it makes sense that a default behaviour of your class is to create an instance, fine, but you can also make other methods look like function calls using apply
.
So far, we’ve been using an apply
method on a singleton object (object Customer
) and dropping the apply
, but you can have apply
methods on a class and call them on a instance variable.
For example, we could create a class called Adder
and call the apply
method on an instance to add two numbers together.
val
add
=
new
Adder
()
add
.
apply
(
1
,
3
)
But we can just as easily drop it and it’ll look like we’re calling a function even though we’re actually calling a method on an instance variable.
// scala
val
add
=
new
Adder
()
add
(
1
,
3
)
Another example is accessing array values. Suppose we have an array of Roman numerals.
val
numerals
=
Array
(
"I"
,
"II"
,
"III"
,
"IV"
,
"V"
,
"VI"
,
"VII"
)
To access the array using an index, the syntax is to use parentheses rather than square brackets.
numerals
(
5
)
// yields "VI'
So using the index in a loop, we could do something like this to print the entire array:
for
(
i
<-
0
to
numerals
.
length
-
1
)
println
(
i
+
" = "
+
numerals
(
i
))
What’s interesting here is that there is an apply
method on array that takes an Int
. So we could have written it like this:
numerals
.
apply
(
5
)
// yields "VI'
for
(
i
<-
0
to
numerals
.
length
-
1
)
println
(
i
+
" = "
+
numerals
.
apply
(
i
))
What looks like language syntax to access an array is actually just a regular method call. Scala fakes it.
update
MethodAssignment works in just the same way. For example, numerals(2) = "ii"
actually calls a special method called update
on the Array
class (def update(i: Int, x: T)
).
numerals
(
2
)
=
"ii"
If Scala sees the assignment operator and can find an update
method with appropriate arguments, it translates the assignment to a method call.
We can apply this idea to our own classes to make an API feel more like language syntax. Let’s say we’re in the business of telephony and part of that business is to maintain a directory of customer telephone numbers.
We can create a collection class to contain our directory, and initialise it to hold the telephone numbers of the four musketeers, like this:
class
Directory
{
val
numbers
=
scala
.
collection
.
mutable
.
Map
(
"Athos"
->
"7781 456782"
,
"Aramis"
->
"7781 823422"
,
"Porthos"
->
"1471 342383"
,
"D`Artagnan"
->
"7715 632982"
)
}
If we decide that the shorthand or default behaviour of the directory should be to return the telephone number of a customer, we can implement the apply
method as follows:
def
apply
(
name
:
String
)
=
{
numbers
.
get
(
name
)
}
That way, after creating a instance of our directory, we can print Athos’s number like this:
val
yellowPages
=
new
Directory
()
println
(
"Athos's telephone number : "
+
yellowPages
(
"Athos"
))
Then if we want to update a number, we could implement an updating method and call it directly. Scala’s assignment shorthand means that if we actually name our method update
, we can use the assignment operator and it will call the update
method for us.
So, we add an update method:
def
update
(
name
:
String
,
number
:
String
)
=
{
numbers
.
update
(
name
,
number
)
}
Then we can call it to update a number like this:
yellowPages
.
update
(
"Athos"
,
"Unlisted"
)
Taking advantage of the shorthand notation, you can also use assignment.
yellowPages
(
"Athos"
)
=
"Unlisted"
update
MethodsWe could also add a second update method, this time with an Int
as the first argument.
def
update
(
areaCode
:
Int
,
newAreaCode
:
String
)
=
{
???
}
Let’s say we want it to update an area code across all entries. We could enumerate each entry to work out which numbers start with the area code from the first argument. For any that match, we go back to the original map and update the entry.
def
update
(
areaCode
:
Int
,
newAreaCode
:
String
)
=
{
numbers
.
foreach
(
entry
=>
{
if
(
entry
.
_2
.
startsWith
(
areaCode
.
toString
))
numbers
(
entry
.
_1
)
=
entry
.
_2
.
replace
(
areaCode
.
toString
,
newAreaCode
)
})
}
The _1
and _2
are Scala notation for accessing what’s called a tuple. It’s a simple data structure that we’re using to treat what, in our case, would be a Map.Entry
in Java as a single variable. The _1
and _2
are method calls that let us access the key and value respectively. Tuples are actually more general purpose than this and not just used for maps. We’re using a tuple of two elements (a Tuple2
) but you can have tuples with up to twenty-two elements (Tuple22
).
We can call the new update
method using the shorthand assignment syntax like this:
object
DirectoryExampleAlternativeUpdateMethod
extends
App
{
val
yellowPages
=
new
Directory
println
(
yellowPages
)
yellowPages
(
7781
)
=
"7555"
println
(
yellowPages
)
}
The outcome of this is that both Athos and Aramis will have their area codes updated.
update
You can have as many arguments in the update
method as you like but only the last will be used as the updated value. This makes sense, as you can only have one value to the right of an assignment operator.
The rest of the argument list is used to select the appropriate update methods. So if you had another method with three arguments (areaCode
, anotherArgument
and newAreaCode
):
def
update
(
areaCode
:
Int
,
another
:
String
,
newAreaCode
:
String
)
=
???
…the types would be used to work out which update
method should be called on assignment.
yellowPages
(
7998
)
=
"7668"
yellowPages
(
7998
,
"another argument"
)
=
"???"
We’ve seen more about the apply
method in this chapter; how you don’t just use it for factory-style creation methods but for building rich APIs. You can make client code more concise by making method calls look like function calls.
We also saw how the related update
method works and in the same way how we can write APIs that take advantage of the assignment operator and implement custom update behaviour.