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