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.
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:
Here we implemented a custom constructor that sets an instance variable to some custom value. The problem with it is pretty obvious. Functions
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):
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:
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.
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:
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 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:
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.
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:
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:
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 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
newkeyword. 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
toStringmethod 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
hashCodemethod is based on case class constructor arguments.
They have a built-in
copymethod 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
CaseClassAwith a modified
Here is an example of how case classes are used in the real world:
This example should be pretty self explanatory. Two case classes
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
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:
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:
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
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 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:
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:
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.
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.