Книга: Learn Scala for Java Developers
Назад: III. Beyond Java to Scala
Дальше: Faking Language Constructs

Faking Function Calls

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.

The apply Method

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

The update Method

Assignment 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" 

Multiple update Methods

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

Multiple Arguments to 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") = "???" 

Summary

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.

Назад: III. Beyond Java to Scala
Дальше: Faking Language Constructs