Scala Code

Scala is a hybrid programming language that implements major functional programming and OOP concepts. There is always more than one way to do something in Scala and sometimes it feels overwhelming. When I first started programming in Scala over a year ago, I was confused as to why it has so many different OOP constructs ranging from straightforward regular and abstract classes to traits, objects, and case classes. In this article I’d like to give an overview of different OOP-inspired structures in Scala and how they are used in real life while keeping your programs functional (i.e., no stupid animal or fish tank examples). I assume that the reader knows some Scala, Java, and a dynamic language like Ruby or Python.

  1. Classes
  2. Objects
  3. Abstract Classes
  4. Traits
  5. Case Classes
  6. Implicit Classes
  7. What’s Next?

Classes

Scala uses class-based inheritance, so it’s no surprise that basic classes are at the core of its OOP model. Classes could be helpful in organizing code. However, if you were to use them like you would use Java classes then a significant problem would arise called mutable state. It means that instance methods are not referentially transparent, which is a no-no in functional programming. Consider the following basic example of a Scala class written in Java style:

class Logger {
  var context: String = "generic"
  
  def this(context: String) = { this(); this.context = context }
  
  def info(m: String) = println(s"INFO: $context: $m")
  def error(m: String) = println(s"ERROR: $context: $m")
}

val l = new Logger()

l.info("test message 1") // outputs `INFO: generic: test message 1`

l.context = "controller"

l.info("test message 2") // outputs `INFO: controller: test message 2`

Here we implemented a custom constructor that sets an instance variable to some custom value. The problem with it is pretty obvious. Functions info() and error() are not referentially transparent since they return different results depending on the hidden mutable state of context. This variable can be changed from the inside and the outside of the object. The latter is true because you don’t need to explicitly define setters and getters.

How can this issue be fixed? There are at least two paths that we can follow. The first way is to use a constructor with private instance variables, so you can’t change them after a new class instance is created (I’m going to use a custom primary constructor for brevity):

class Logger(private var context: String = "generic") { 
    def info(m: String) = println(s"INFO: $context: $m")
    def error(m: String) = println(s"ERROR: $context: $m")
}

val l = new Logger("controller")

l.info("test message 1") // outputs `INFO: controller: test message 1`

// the following line will generate an error:
// `variable context in class Logger cannot be accessed in Logger`
l.context = "model"

l.info("test message 2") // outputs `INFO: controller: test message 2`

Now no outside object can change the state of context. This solution is a little better but not the best. Why? Because Logger’s internal methods can still potentially modify context resulting in broken referential transparency. Also, we can’t read instance variables anymore without creating getters (in Scala we almost never want to do this).

So, what’s the ultimate solution? To eliminate mutable state we have to use read-only variables. The only thing that has to be changed from the previous example is the class signature:

class Logger(val context: String = "generic")

This way instance variables can’t be modified internally or externally and we still have read access to them. But what happens if at some point in your program you need to change some property of a class instance? Like with almost everything in functional programming, you’ll have to create a new instance of your class to avoid mutable state.

One big surprise if you are coming from Java or a Java-like language is that Scala doesn’t have static class members. It provides a singleton construct called object where all members are static. Because of this separation of concerns Scala could be considered more object-oriented than Java.

Objects

Object is a special type of class. It can only be instantiated once, which makes it a singleton. All object’s members are static. Here is an example of an object:

object Cache extends LegacyJavaRedisAdapter {
  val a = new RedisInMemoryAdapter
  
  def get(k: String): Option[String] = a.get(k) match {
    case v: String => Some(v)
    case _ => None
  }
  def set[A](k: String, v: Option[A]): Boolean = storage.set(k, v)
}

Cache.set("testKey", Some("testValue")) // returns `true`
Cache.get("testKey") // returns `Some("testValue")`
Cache.get("nonExistentValue") // returns `None`

You’d normally use objects for the same purposes that you’d use singletons in Java. Logging or caching utilities are great examples of singletons. Some choose to implement them with dependency injection but in most cases, in Scala, it’s an overkill. Another great use for objects is shared resource control, like managing a pool of connections to a database for the whole application or writing to a file.

At first glance, objects are just like traditional singletons but there is more than meets the eye, which makes them really attractive and usable in contrast with anti-pattern-y Java singletons.

One important distinction is that objects are polymorphic and can extend and implement other OOP constructs. You can see it in the example above where we extended some arbitrary legacy Java class for our Scala application. This polymorphism means that you can easily inject objects as dependencies without all the tight coupling headaches and global state associated with Java singletons.

Another big distinction between objects and traditional singletons is that Scala objects take care of the boilerplate code. It’s hard to overstate this. Imagine that you don’t need to implement the getInstance() method for every one of your singletons nor do you need to instantiate a singleton instance in other classes! This approach promotes the single responsibility principle and makes for easy testing.

There is more to be said about objects later once I introduce more OOP concepts.

Abstract Classes

Abstract classes in Scala are very similar to Java: they can only be subclassed and never instantiated. Also, abstract classes can have both constructor and type parameters.

In most cases you would want to use abstract classes in Scala if you are planning on inheriting from them in your Java code or if you want to distribute them in compiled form. There are some other cases where abstract classes are preferable, like untyped lambda calculus hierarchies (since case-to-case inheritance in case classes is prohibited in Scala) or required constructor arguments. As a general rule of thumb, just stick with Scala traits in most circumstances. Traits are discussed later in this article.

Here is an example of an abstract class in Scala:

abstract class BaseModel[A](db: Database, table: String) {
  val id: Int // abstract value
  val t = TableHelper(db, table)

  def toJson: String // abstract method
  def get = t.get(id)
  def save: Option[Int] = t.save(toJson)
  def update: Boolean = t.update(toJson)
  def delete: Boolean = t.delete(id)
}

case class PostTemplate(title: String, body: String)

class Post(val id: Int, val template: PostTemplate) extends BaseModel[PostTemplate](new Database, "posts") {
  def toJson = s"""{"id": $id, "title": "${template.title}", "body": "${template.body}"}"""
}

val p = new Post(1, PostTemplate("Scala OOP Galore", "Scala is a hybrid..."))
p.toJson // returns `{"id": 1, "title": "Scala OOP Galore", "body": "Scala is a hybrid..."}`

In Java, one inherent limitation of abstract classes is that concrete classes or objects can only inherit from one abstract class limiting multiple inheritance and promoting inheritance over composition, which in most cases is undesirable. In Scala there is no such problem because it allows multiple inheritance with traits.

Traits

If you are coming from a language that allows mixins (e.g., modules in Ruby), traits will look familiar. In a nutshell, they are components of classes that can be stacked together. Traits are akin to Java interfaces, but are allowed to have method implementations.

One big difference between classes and traits is that traits don’t support constructor parameters but have type parameters. Traits are supposed to be very minimal and focus on one responsibility. This way multiple traits can be stacked together. This OOP principle is called composition over inheritance and is generally a good practice to follow because it allows for better extensibility and flexibility of your programs. Here is an example of traits in Scala:

trait Boldable {
  def bold(text: String): String = s"**$text**"
  def unbold(text: String): String = ???
}

trait Italicizable {
  def italicize(text: String): String = s"*$text*"
  def unitalicize(text: String): String = ???
}

object MarkdownWrapper extends Boldable with Italicizable {
  def boldAndItalic(text: String): String = bold(italicize(text))
}

MarkdownWrapper.wrapWithBoldAndItalic("Scala rocks!") // returns `***Scala rocks!***`

How does Scala solve the multiple inheritance problem in traits also known as the Diamond Problem? The solution is fairly straightforward: it uses overridden members from right to left. The implementation on the right always wins over the implementation on the left. For example:

trait GenericStream {
  val stream: Stream[Int]
}

trait IntegerStreams extends GenericStream {
  override val stream = Stream.from(1)
  val oddsStream: Stream[Int] = Stream.from(1, 2)
  val evensStream: Stream[Int] = Stream.from(2, 2)
}

trait FibonacciStream extends GenericStream {
  override val stream: Stream[Int] = 0 #:: stream.scanLeft(1)(_ + _)
}

object FunkyMath extends IntegerStreams with FibonacciStream {
  def generateIntegers(n: Int): List[AnyVal] = stream.take(n).toList
}

generateIntegers will use the FibonacciStream.stream implementation in this case. To get multiple inheritance to work you must use an override keyword otherwise you’ll get a compile exception about conflicting members.

You can learn more about traits in this article that digs into other trait uses and explains how delegating to super works. Another great read on traits is about a “cake pattern”, which is the Scala way to do dependency injection.

Case Classes

Case classes are immutable data-holding entities that are primarily used for pattern matching. They might seem weird to beginner Scala developers at the outset but you will start using them more and more with practice. Case classes are almost like classes but with some extra properties:

  • They have a short initialization syntax without the new keyword. This property makes it really easy to decompose them using pattern matching. Case class primary constructors automatically use vals for parameters, so they are immutable by default.

  • They have a built-in toString method that generates a string with the case class name and its constructor arguments.

  • They have built-in equality comparisons. This means that you can compare two instances of the same case class like this: CaseClassA(25) == CaseClassA(26) without implementing equals by hand.

  • The hashCode method is based on case class constructor arguments.

  • They have a built-in copy method that makes a copy of a case class instance with custom parameter values rewritten. For example CaseClassA(25).copy(SomeParameter = 26) will return a new instance of CaseClassA with a modified SomeParameter.

Here is an example of how case classes are used in the real world:

trait Resource {
  def fullPath: String
}

case class Folder(name: String = "", path: Option[String] = None) extends Resource {
  def fullPath: String = path match {
    case Some(p) => List(p, name).mkString("/")
    case None => s"./$name"
  }
}

case class File(name: String, folder: Option[Folder] = None) extends Resource {
  def fullPath: String = folder match {
    case Some(f) => List(f.fullPath, name).mkString("/")
    case None => s"./$name"
  }
}

val resources = Vector[Resource](
  File("ex1.scala", Some(Folder("example", Some("~/dev")))),
  Folder("tmp"),
  Folder("bin", Some("/usr")),
  File(".zshrc")
)

resources foreach {
  case f: File => println(s"File: ${f.fullPath}")
  case f: Folder => println(s"Folder: ${f.fullPath}")
}

// the above code outputs:
//
// File: ~/dev/example/ex1.scala
// Folder: ./tmp
// Folder: /usr/bin
// File: ./.zshrc

This example should be pretty self explanatory. Two case classes Folder and File are created with some default constructor values. Both case classes inherit from a trait with an abstract fullPath method. Then we define a Resource vector that can contain Resources, Folders, and Files (polymorphism for the win!). Finally, we loop over the vector and match its elements based on case classes that in this example act like types. It’s an extremely powerful tool that can save tons of boilerplate code and make programs a lot more readable.

How can a case class be magically instantiated without the new keyword? What lies beyond this syntactic sugar? All case classes have so called companion objects that are automatically created for the default constructor. Here is what happens in the background of the Folder case class from the previous example:

case class File(name: String, folder: Option[Folder] = None) extends Resource {
  def fullPath: String = folder match {
    case Some(f) => List(f.fullPath, name).mkString("/")
    case None => s"./$name"
  }
}

object Folder {
  def apply(name: String, folder: Option[Folder] = None) = new File(name, folder)
}

Object Folder is created automatically by Scala. So, when you type Folder("tmp") what really happens is you initialize the Folder object first and then the apply method (also called a factory method) creates an instance of a case class for you. How is this helpful? For starters, you don’t need to write the new keyword every time you initialize a case class—it really reduces visual clutter in complex hierarchies. The second reason is that you can define your own companion objects for regular classes and case classes. It’s really helpful in situations when you want to do something with your constructor arguments. Here is a more advanced example where we pass two different entities to the companion object/case class pair and end up with an instance defined in the primary constructor:

import scala.util.{Failure, Success, Try}

case class Product(name: String, url: Option[String])
case class User(name: String, fullName: String, age: Int)

object Customer {
  def apply(u: User, p: Product): Option[Customer] = Try {
    require(u.age >= 0)
    require(u.name.matches("^[a-zA-Z0-9]*$"))

    new Customer(u, p)
  } match {
    case Success(c) => Some(c)
    case Failure(e) => None
  }
}

case class Customer(userName: String, projectName: String, age: Int) {
  def this(u: User, p: Product) = this(u.name, p.name, u.age)
}

Customer(
  User("vasily", "Vasily Vasinov", 26),
  Product("Amazon EC2", None)
)

// the above code returns `Some(Customer(vasily,Amazon EC2,26))`

What did we just do? Firstly, we defined a custom constructor in the Customer case class that calls the primary constructor with decomposed values from Product and User instances. Secondly, we created a custom companion object with an apply method that matches a custom constructor from the case class. In this method we do some static parameter checking and return Some(Customer) on success and None on failure.

Pretty cool, huh? This pattern is quite popular in Scala code and for good reason: your code becomes better separated and more readable.

Implicit Classes

Implicit classes are really powerful extension tools for other classes. Anyone coming from Ruby will recognize implicit classes as an alternative to monkey patching. In Ruby you would write something like this if you wanted to extend an existing class without introducing new classes:

class Fixnum
  def odd?
    self % 2 != 0
  end
  
  def even?
    self % 2 == 0
  end
end

21.even? # returns false

Examples like this are usually used to show the power of Ruby. It’s certainly very impressive but Scala can do something very similar but better. Consider the following example:

object IntSandbox extends App {
  implicit class IntWithHelpers(val x: Int) {
    def isOdd = {
      x % 2 != 0
    }
    
    def isEven = {
      x % 2 == 0
    }
  }

  21.isEven // returns false
}

We just defined an implicit IntWithHelpers class with a single constructor value of type Int. This instructs the compiler to implicitly convert any Int in scope to IntWithHelpers that inherits all Int members. One huge difference between Scala and Ruby here is that Scala only applies the implicit in the current scope, which means that you can import your implicit classes wherever you need them without littering the global namespace. This allows for less collisions and more compact DSLs.

You have to remember about three limitations when creating implicit classes. First, they have to be defined inside of another trait, class, or object. Second, you can only have one non-implicit argument in the constructor. And lastly, you can’t have another object or member in scope with the same name, which implies that case classes can’t be implicit since they have a companion object with the same name.

To be fair to Ruby, I should notice that there is a proposed change to the spec that adds local monkey patching. It’s called a refinement. It exists as an experimental feature in Ruby 2.0 and is expected to exist in future versions of Ruby.

What’s Next?

By now you probably got a pretty good feel of what’s possible in Scala in terms of functional OOP. I’d recommend looking at more production code examples and experimenting with different scenarios that require object-oriented techniques. There are certainly more hidden things about OOP structures than described in this article but I’ll leave it to you to explore :).

Please share any feedback or corrections with me. It’s always hugely appreciated.