The Road to Functional Programming

Educational material to learn functional programming in Scala, from scratch, in a structured and comprehensive way.

View project on GitHub

A quick tour of the Scala object keyword

Estimated reading time: 4 minutes

This chapter is a little refresher on what the Scala object keyword does. You can complement this page with a well written full article here.

Singleton objects

Scala classes cannot have static variables or methods like other languages, like Java, do. Instead a Scala class can have what is called a singleton object which is a special instance of a class that the compiler guarantees to be unique and that makes available in scope without an explicit instantiation.

A singleton object is declared using the object keyword:

object MyComponent {
  val enabled: Boolean = false
  private val otherConfig: String = "Some config"
}

MyComponent.enabled

// Output:
//   Boolean = false

By all means, this is a language-level implementation of the well known Singleton pattern.

Modules

Another use case for objects is to use them as containers to modularize the application. In this scenario you stop thinking of object as related to OOP and FP and you create an object when you want to organize your code and place things in a separate place from others. You can also next object one inside the other and give a hierarchical structure to your code. It’s a bit like packages but it gives the developer different features that packages don’t. I won’t go through these differences as they’re not that important but the point is that you can use object to organise your code. Here is an example of how you can use the keyword:

object UserModule {
  def createUser(name: String, surname: String): String = s"$name $surname"
  // ... more methods, variables, classes and so on here ...
}

object PetModule {
  object PetOperations {
    def cleanPet(pet: String): String =  s"$pet, cleaned"
    // ... more methods, variables, classes and so on here ...
  }
  def createPet(name: String, age: Int): String =  s"$name, age: $age"
  // ... more methods, variables, classes and so on here ...
}

// Importing from an object
import UserModule._
createUser("Matt", "Smith")

// Accessing the object's members
val pet = PetModule.createPet("fido", 5)
val cleanedPet = PetModule.PetOperations.cleanPet(pet)

// Output:
//   pet: String = "fido, age: 5"
//   cleanedPet: String = "fido, age: 5, cleaned"

Companion Objects

When a singleton object is named the same as a class and it is defined inside the same source file, it is called a companion object.

A companion object and its class can access each other’s private members (fields and methods) and this becomes useful whenever you want them to interact. For example you can create a class with a private constructor but because the companion object can access the private constructor you could mediate the creation of an instance. Viceversa you could set private constants in the companion object that only the class will be able to use. These are only two examples of the features of companion objects.

object Container { // Container is used to make the example work on REPL

  object MyComponent {
    val enabled: Boolean = false
    private val otherConfig: String = "Some config"
  }

  class MyComponent(name: String) {
    val companionOtherConfig = MyComponent.otherConfig
  }

}
import Container._

new MyComponent("My name").companionOtherConfig

// Output:
//   String = "Some config"

When you define a special method named apply() in an object, the compiler can call it without explicitly naming this method which means that instead of writing MyObject.apply(...) you simply write MyObject(...). This is useful in a few cases:

  • to call methods with a shorter syntax;
  • to shorten the instantiation of new classes (omitting new);
  • to provide several simplified constructors.

Here is a short example:

class Loader(val name: String)

object Loader {
  def normal(): Int = 123
  def apply(): Int = 5
  def apply(name: String): Loader = new Loader(name)
  def apply(first: String, second: String): Loader = new Loader(s"$first $second")
}

Loader.normal()             // As usual
Loader()                    // Equivalent to Loader.apply()
Loader("Matt Smith").name
Loader("Matt", "Smith").name

// Output:
//   Int = 123
//   Int = 5
//   String = "Matt Smith"
//   String = "Matt Smith"

Object type

It’s worth nothing that a singletone object is itself a value. When you refer to the MyComponent as we do above you’re actually referencing the singletone instance created by the compiler. The type of the singletone is accessed using the .type syntax, like MyComponent.type. This has implications because it means you can store and pass around the singletone instance:

val aBigDecimal: BigDecimal = BigDecimal(123.45)
val theBigDecimal: BigDecimal.type = BigDecimal
val fromStringBigDecimal: BigDecimal = theBigDecimal.apply("123.45")

// Output:
//   aBigDecimal: BigDecimal = 123.45
//   theBigDecimal: BigDecimal.type = scala.math.BigDecimal$@772485dd
//   fromStringBigDecimal: BigDecimal = 123.45

This can also be source of confusion because there are several similar syntaxes. Let’s have a look at the following example with comments:

// A class
class TestClass

// An object
object TestObject {
  def apply(): TestClass = new TestClass()
}

// A new instance of TestClass
val a: TestClass = new TestClass()

// The singletone instance of the object TestClass
val b: TestClass.type = TestClass

// A new instance of TestClass obtained by calling the .apply() method on the object TestClass
val c: TestClass = TestClass()

Pattern matching

There is another special method that can be defined in a object called unapply() and it is known as extractor. This method is used to “deconstruct” objects into its components, or “extract” the components, that are returned by the method and it’s used by pattern matching to perform its duty. We’ll explain more about pattern matching in the next chapter. In the following example we can see how both apply() and unapply() are used to construct and deconstruct a sample object:

class Person(val name: String, val age: Int)

object Person {
  def apply(name: String, age: Int): Person = new Person(name, age)
  def unapply(p: Person): Tuple2[String, Int] = (p.name, p.age)
}

val matt = Person("Matt Smith", 30)
Person.unapply(matt)

// Output:
//   (String, Int) = ("Matt Smith", 30)

If we want to use extractors into pattern matching the unapply method must return a type that has a isEmpty and a get method as explained here. For example we can make unapply() return an Option because Option has the two methods isEmpty and get:

class Person(val name: String, val age: Int)

object Person {
  def apply(name: String, age: Int): Person = new Person(name, age)
  def unapply(p: Person): Option[(String, Int)] = Some(p.name, p.age)
}

val matt = Person("Matt Smith", 30)

matt match {
  case Person(name, age) => s"Name: $name, Age: $age"
}

// Output:
//   String = "Name: Matt Smith, Age: 30"

References