Scala Functional Programming Patterns
上QQ阅读APP看书,第一时间看更新

Scala singletons

Scala has singleton objects called companion objects. A companion object is an object with the same name as a class. A companion object also can access private methods and fields of its companion class. Both a class and its companion object must be defined in the same source file. The companion object is where the apply() factory method may be defined. Let's have a look at the following example of a companion class:

class Singleton {  // Companion class
  def m() {
    println("class")
  }         
}

And then its companion object as:

object Singleton { // Companion Object
  def m() {
    println("companion")
  }         
}

It is that simple, when a case class is defined, Scala automatically generates a companion object for it.

The apply() factory method

If a companion object defines an apply() method, the Scala compiler calls it when it sees the class name followed by (). So, for example, when Scala sees something like:

                 Singleton(arg1, arg2, …, argN) // syntactic sugar

It translates the call into:

                 Singleton.apply(arg1, arg2,...,argN) 

Open a Scala console and enter the following:

scala> val p = Map("one" -> 1, "two" -> 2)
p: scala.collection.immutable.Map[String,Int] = Map(one -> 1, two -> 2)

When we say Map("one" -> 1, "two" -> 2), it seems like we are calling a function named Map with the arguments—however, this form is just syntactic sugar. Under the hood, Map has a companion object whose apply method is being called, then the apply() method creates the Map object and assigns it to p.

Every object can be called as a function, provided it has the apply method. For example, we can add an apply method ourselves and get the benefit of the syntactic sugar. The following uses REPL's paste mode. The paste mode, enabled via the paste command, allows you to enter multiline code snippets for evaluation:

scala> :paste
// Entering paste mode (ctrl-D to finish)
class C(x: Int) {
 def print() = println(x)
}
object C {
 def apply(n: Int) = {
 new C(n)
 }
}

press Ctrl + D here:

// Exiting paste mode, now interpreting.
defined class C
defined object C
scala> val k = C(9)
k: C = C@4b419a6b
scala> k.print()
9

The expression C(9) looks very much like a function call. As we see it is expanded to C.apply(...).

Wait a minute! Does this apply to functions, too? Yes:

scala> val f = (l: List[Int]) => l.map(_ * 2)

f: List[Int] => List[Int] = <function1>

scala> f(List(1,2,3))

res0: List[Int] = List(2, 4, 6)

When we call f(arg) it gets translated to f.apply(arg).

The factory method pattern

A factory method can further hide the actual concrete class implementing an interface. You have the factory method pattern, shown as follows:

Figure 2.4: Factory Method UML

Also let's try the following program:

public interface Currency {
 public String getConversionRateToIndianRupee();
}
public class CurrencyConverter {
 private static final String YEN = "Yen";
 private static final String EURO = "Euro";
 private static final String DOLLAR = "Dollar";

 private static final class EuroToRupee implements Currency {
  @Override
  public String getConversionRateToIndianRupee() {
   return "82";
  }
 }

 private static final class DollarToRupee implements Currency {
  @Override
  public String getConversionRateToIndianRupee() {
   return "60";
  }
 }
 private static Currency createCurrencyFor(final String currencyStr) {
  if (currencyStr.equals(DOLLAR)) {
   return new DollarToRupee(); 
  }
  if (currencyStr.equals(EURO)) {
   return new EuroToRupee();
  }
  throw new IllegalArgumentException("Oops! no idea about <"
    + currencyStr + ">");
 }

 public static void main(String[] args) {
  System.out.println(createCurrencyFor(DOLLAR)
    .getConversionRateToIndianRupee());
  System.out.println(createCurrencyFor(EURO)
   .getConversionRateToIndianRupee());
  System.out.println(createCurrencyFor(YEN)
    .getConversionRateToIndianRupee());
 }
}

All the knowledge of the conversion rule is in one place and hidden by design from the outside world. This hiding helps us edit existing rates and extend for more currencies all in one place. The big advantage is we get a single point where we can change, knowing the change will correctly reflect everywhere.

The Scala version

The Scala version is simpler, we just use the apply method:

trait Currency {
 def getConversionRateToIndianRupee: String
}

object CurrencyConverter {
 private object EuroToRupee extends Currency {
 override def getConversionRateToIndianRupee = "82"
 }

 private object DollarToRupee extends Currency {
 override def getConversionRateToIndianRupee = "60"
 }

 private object NoIdea extends Currency {
 override def getConversionRateToIndianRupee = "No Idea"
 }

 // the currency factory method
 // Note: Scala if statement is an expression. 
 def apply(s: String):Currency = {
 if (s == "Dollar") // same as s.equals("Dollar") in Java 
DollarToRupee
 else if (s == "Euro")
 EuroToRupee
 else
 NoIdea
 }
}

val c = CurrencyConverter("Dollar") // apply method in action 
c.getConversionRateToIndianRupee // outputs "60"

Point to ponder: How could we rewrite the apply(...) method?

The apply method has one if expression, its value is the return value.

Note

Difference from Java. In Scala, the if/else statement has a value.

This allows me to write:

scala> def m(x: Int) = if (x < 0) true else false
m: (x: Int)Boolean

scala> m(10)
res35: Boolean = false

scala> m(-10)
res36: Boolean = true