May 13, 2021 Scala
The apply method gives you a good syntax sugar when a class or object has a primary purpose.
scala> class Foo {}
defined class Foo
scala> object FooMaker {
| def apply() = new Foo
| }
defined module FooMaker
scala> val newFoo = FooMaker()
newFoo: Foo = Foo@5b83f762
Or
scala> class Bar {
| def apply() = 0
| }
defined class Bar
scala> val bar = new Bar
bar: Bar = Bar@47711479
scala> bar()
res8: Int = 0
Here, our instantiated object looks like it's calling a method. There will be more introductions in the future!
A singleton object is used to hold a unique instance of a class. Typically used in factory mode.
object Timer {
var count = 0
def currentCount(): Long = {
count += 1
count
}
}
You can use this:
scala> Timer.currentCount()
res0: Long = 1
A singleton object can have the same name as a class, at which point the object is also known as a companion object. We usually use companion objects as factories.
Here's a simple example where you
new
use new to create an instance.
class Bar(foo: String)
object Bar {
def apply(foo: String) = new Bar(foo)
}
In Scala, we often talk about functional programming of objects. W hat does that mean? What exactly is a function?
A function is a collection of traits. S pecifically, a function with an argument is an example of a Function1 trait. This feature defines the apply() syntax sugar, allowing you to call an object as if you were calling a function.
scala> object addOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined module addOne
scala> addOne(1)
res2: Int = 2
This Collection of Function Traits is labeled from 0 to 22. W hy 22? T his is a subjective magic number. I've never used more functions than 22 parameters, so this number seems reasonable.
Apply syntax sugar helps unify the two-way nature of objects and functional programming. You can pass classes and use them as functions, which are essentially instances of classes.
Does this mean that when you define a method in a class, what you get is actually
Function*
N
o, the methods defined in the class are methods, not functions.
The method that is defined independently in repl is an instance of
Function*
Classes can also extend Function, and instances of these classes can use () calls.
scala> class AddOne extends Function1[Int, Int] {
| def apply(m: Int): Int = m + 1
| }
defined class AddOne
scala> val plusOne = new AddOne()
plusOne: AddOne = <function1>
scala> plusOne(1)
res0: Int = 2
You can replace the extends Function1
extends (Int => Int)
with a more intuitive and
extends Function1[Int, Int]
( Int
class AddOne extends (Int => Int) {
def apply(m: Int): Int = m + 1
}
You can organize the code in a package.
package com.twitter.example
Defining a package at the head of the file declares all the code in the file in that package.
Values and functions cannot be defined outside of classes or singleton objects. A singleton object is an effective tool for organizing static functions.
package com.twitter.example
object colorHolder {
val BLUE = "Blue"
val RED = "Red"
}
You can now access these members directly
println("the color is: " + com.twitter.example.colorHolder.BLUE)
Note the return of the Scala interpreter when you define this object:
scala> object colorHolder {
| val Blue = "Blue"
| val Red = "Red"
| }
defined module colorHolder
This implies that Scala's designers designed objects as part of Scala's modular system.
This is one of the most useful parts of Scala.
Match value
val times = 1
times match {
case 1 => "one"
case 2 => "two"
case _ => "some other number"
}
Match with guards
times match {
case i if i == 1 => "one"
case i if i == 2 => "two"
case _ => "some other number"
}
Notice how we assign values to
i
In the last line of instructions, it is a wildcard;
Otherwise, when you pass in a number that cannot be matched, you get a runtime error. We'll continue to discuss this topic in the future.
Refer to Effective Scala's recommendations for when to use pattern matching (http://twitter.github.com/effectivescala/#Functional programming-pattern matching) and pattern matching formatting (http://twitter.github.com/effectivescala/#Formatting-Pattern matching). A Tour of Scala also describes pattern matching.
You can use match to work with different types of values separately.
def bigger(o: Any): Any = {
o match {
case i: Int if i < 0 => i - 1
case i: Int => i + 1
case d: Double if d < 0.0 => d - 0.1
case d: Double => d + 0.1
case text: String => text + "s"
}
}
Remember our previous calculator?
Let's classify them by type.
def calcType(calc: Calculator) = calc match {
case _ if calc.brand == "hp" && calc.model == "20B" => "financial"
case _ if calc.brand == "hp" && calc.model == "48G" => "scientific"
case _ if calc.brand == "hp" && calc.model == "30B" => "business"
case _ => "unknown"
}
Using sample classes makes it easy to store and match the contents of the class. You don't need the new keywords to create them.
scala> case class Calculator(brand: String, model: String)
defined class Calculator
scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)
The sample class automatically implements the equal and readable toString method based on the parameters of the constructor.
scala> val hp20b = Calculator("hp", "20b")
hp20b: Calculator = Calculator(hp,20b)
scala> val hp20B = Calculator("hp", "20b")
hp20B: Calculator = Calculator(hp,20b)
scala> hp20b == hp20B
res6: Boolean = true
Sample classes can also have methods like normal classes.
Sample classes are designed for pattern matching. Let's simplify the example of the previous calculator classifier.
val hp20b = Calculator("hp", "20B")
val hp30b = Calculator("hp", "30B")
def calcType(calc: Calculator) = calc match {
case Calculator("hp", "20B") => "financial"
case Calculator("hp", "48G") => "scientific"
case Calculator("hp", "30B") => "business"
case Calculator(ourBrand, ourModel) => "Calculator: %s %s is of unknown type".format(ourBrand, ourModel)
}
The last sentence can be written in this way
case Calculator(_, _) => "Calculator of unknown type"
Or we could not specify a match object as a Calculator type
case _ => "Calculator of unknown type"
Or we can rename the matching values.
case c@Calculator(_, _) => "Calculator: %s of unknown type".format(c)
Exceptions in Scala can be used through pattern matching in the try-catch-finally syntax.
try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
} finally {
remoteCalculatorService.close()
}
Try is also expression-oriented
val result: Int = try {
remoteCalculatorService.add(1, 2)
} catch {
case e: ServerIsDownException => {
log.error(e, "the remote calculator service is unavailable. should have kept your trusty HP.")
0
}
} finally {
remoteCalculatorService.close()
}
This is not a perfect demonstration of programming style, but an example of how try-catch-finally is an expression like most other things in Scala.
When an exception is caught and processed, the final block is called;