Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Basics (continued)


May 13, 2021 Scala


Table of contents


Basics (continued)

The apply method

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!

Singleton object

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)
}

A function is an object

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
}

Package

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.

Pattern match

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.

Match type

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

Match class members

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

Sample class Case Classes

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.

Use sample classes for pattern matching

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)

Abnormal

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;