Hello there, and welcome to Episode Three of ProScala podcasts. This is the third tech episode released on the 14th of June, 2021. We will go into detail with some tech topics targeting both newbies, senior, and business audiences. I’m Csaba Kincses, we’ll start in a moment.
We will concentrate on one topic today, concretely the object oriented features of Scala. This is a broad topic and it can be approached in many ways. Considering previous episodes, this show tried to keep track of two aspects both taking into account paradigm-wise theory and practical arguments from the business side and agile while using technological story lines. This time I’m about to introduce a shift towards practicality to keep it short as this chapter will seek answers to the question whether Scala’s approach to Functional can considerably add significant enrichment that would coalesce into something that gives project level benefits.
As you see, we are talking about object oriented programming, but what does that have to do with a functional language? There is no exact definition regarding where is a border between these two paradigms, so there is some flexibility here in terms of interpretation, at least we can be sure that they are not the opposite. Since there is data that needs structuring, OOP can help us, Scala’s offer here is not that paradigm focused, it simply implements many features that could be familiar for a Java programmer for example.
While keeping the focus on what this set of features coalesce into in terms of development efficiency, we can still have some paradigm-wise examination basically iterating the possibilities starting from the ones close to pure Functional then going farther from this.
For the reason this show is to promote Functional, even if in OOP terms Scala comes up with hybrid solutions, I treat it important to describe those unclear distinctions between the possible solutions to enable you to move toward Functional.
It’s important as well to make it clear that business-wise we won’t be able to picture a clear attachment to agile gains as we were able to examine the effect of monadic behavior and functional patterns previously. If we want to examine agile, we may examine the ways Scala features can be aligned with domain driven design.
As most of the terms at least on the developer side could be easily interpreted, the best thing we can do to give an outlook is enumerating the available features and then go in order to describe which does what.
To give you a short overview, that is ordered on some score I’m trying to give based on distance from something we may treat as pure Functional and the specific importance in Scala language, the OOP features would follow as such:
General, but important feature is that in Scala everything is an object, it has its type system to handle everything that could be a “non-object special value” in another language, as an object.
Second noteworthy feature are case classes, which combines a few features to give you solutions in regards to writing concise code, also it can be treated as a structure that can be applied in a way we can treat as being close to pure Functional.
Scala provides the possibility of implementing yet another thing known from functional languages, namely the possibility of implementing type classes.
What is something Scala-ish is that we can do multiple inheritance with Scala’s traits, and also traits can have self types to express dependencies.
Scala has classes and objects, which are similar to the ones you may know from an imperative language, though Scala adds some extra to it like letting you simplify the singleton pattern.
Scala also has type constraints.
By iterating through these, we will focus on what are those of the latter which are not obvious, and how to use them.
As said, in Scala everything is an object, meaning that functions are objects as well, this is the most obvious one being the paradigm maker, as it makes it available for us to pass around functions as callbacks. Still important, though maybe rarely used compared to the first one, is that we can easily modify the behaviour of constructs that are for example being primitive types in Java, meaning not being objects. So, in Scala, a simple number is an object, meaning the operators like an addition are methods you can override, giving us some flexibility.
As you can guess, a case class is something similar to a class which we also have in Scala, but with some extras, and this is the point in our examination when the distance from pure functional issues surfaces. What helps us to make a distinction in possible usages of this feature? It would be easy to say its using a case class as a constant as even though Scala provides an option to do something imperative as overriding a by default constant value in a case class with a variable, that’s not the way this feature is intended to be used.
Another factor in checking how far we are from being pure functional is whether we would want to close values and methods in one block together? Though you can do that with a case class, it is not designed to do this. Simply put, a case class is a simple one-liner data structure that is supposed to be immutable, offering value-wise comparability and you can use it in pattern matching. So we can pin that, this is the most pure functional OOP solution in the language, if used as was designed for.
Once you need something different like adding some methods to your class which can be controversial while arguing right functional design, but you definitely can do this, in many cases it could be cumbersome not to do it and if you come with a for example Java background, this can be familiar for you, in this case using a normal class or an object is the solution.
You may ask, what’s a best practice regarding Scala data structures like case classes and normal classes, for example when would be more convenient to completely avoid putting values and methods together? Let’s look at the case, when your application is built on the actor model utilizing case classes to implement communication. If the message handlers that receive the case class messages do just something as simple that you do not need a class hierarchy to implement these, then you probably do not need to put values and functions together, but that sounds like an edge case.
There are also case objects which you can easily use to create typed enumerations, having each case object inherit from the same abstract parent class. These can also be pattern matched and as they can inherit from another type, matching against types can also be utilized, you can implement hierarchized matches if you wish. Same applied regarding type based matching for case classes.
Bottom line is use a case class if you want to handle a simple set of data and go with immutability, treat the case class instance as a comparable value constant, you have everything pre-implemented for you so that you can easily check the value equality of the contained value set, and pattern matching has an easy syntax too. A case class is for all the cases, when you have data but no logic directly attached.
Scala supports the implementation of Type Classes, though due to the presence of a more than complete inheritance implementation referring to multiple inheritance which we will check out later, and that we can use normal classes, it may only be used for a limited range of cases. Type Classes are more commonly used in other functional languages, but only for that reason it won’t be necessary to use it, but that can depend on your programming style.
Type Classes offer less coupling, as using this feature, you probably have a function that would accept a value having a type, and would be capable to return with a result if a type specific implementation is present for the received type, otherwise it will result in an error. Bottom line is this pattern provides us with different implementations based on a variety of parameters with various types, these types can be unrelated, so that we can solve our problem of providing polymorphism without using inheritance.
Now we have come to the point of talking about traits. Traits are the most important Scala feature in terms of object oriented programming as they let us implement multiple inheritance, express dependencies and they even let us compose classes in an ad-hoc way, creating unnamed classes, all of these provide us flexibility.
Traits are similar in a way to Java interfaces with the important difference that they can contain implementation for functionality, so though it won’t be a proper definition but I believe, could be helpful, that you can imagine traits as being interfaces with code, not just method footprints.
You may ask how multiple inheritance is implemented, well, it slightly differs from other languages, where this feature is present. Firstly, we use traits being pieces of abstract or implemented functionality instead of classes, so you won’t find a thing like one class extending two others, but you can mix in traits containing different implementations of a functionality having the same name.
As said, this way, mixing in a series of traits there is the possibility of having two conflicting implementations of a method by naming, there comes the most common problem with multiple inheritance. Scala solves this in a way that does not require any special handling from the developer than keeping in mind that there is a specific order how Scala applies the different versions of functions to resolve ambiguities. Concretely, if we have a sequence of traits mixed in to another, and the traits contain multiple versions for the same method, then the last one in the sequence will be applied.
Same order applies with the other functionality provided by traits, namely the stackable traits pattern, meaning we have the possibility to create an object instance composed from traits without giving a name to that specific type. This comes with the fact that having a set of traits, we can create multiple combinations, and in case of letting the reader of the source code know on the spot what were the traits used and in what order they were mixed in, is more expressive than giving every combination a name, then this is the solution to go with.
Another feature we get from traits is that they can have self types restricting what you can extend with them. This is a form of expressing dependencies between structures that will be enforced at compile time.
Considering classes and objects we have to reiterate the relation of these to the singleton pattern and we may check some other syntax compacting benefits, and then we may make a distinction between a right functional style usage and an imperative one.
The possibility to implement a singleton pattern in Scala in a simplified way compared to for example Java, where you would need a private variable to store an instance and a constructor always returning the same instance was already mentioned in Episode One highlighting the calculable benefit in the form of saved lines of code. After reiterating this, I need to add some additions like the existence of companion objects, and what comes from this considering a singleton pattern.
So okay, a singleton can be simplified to a one liner using an object keyword, but having a class with the same name will result in a somewhat different pattern from a classic singleton pattern. If these are in pairs, then the object will become a companion object and we will have a class that can have different instances, so in this case the original purpose of a singleton pattern like providing us a reliable solution that we always deal with the same instance, is ruined, in this case we have other possibilities.
It’s clear that if the object keyword does not stand alone but in pair with a class, then we’re not talking about a singleton anymore, but we can utilise the presence of the object keyword for other patterns. Scala gives us a neat way to create something similar to a factory pattern, as apply methods can be specified in different shapes, having these we can use a syntactic sugar where we don’t even need to write the name of the apply method, we can simply write the parentheses for parameters after the name of the class - object pair. This is a nice way to hide the usage of the new keyword, as we may only use it a level lower than we would otherwise, being used just in the apply method.
Though it’s not the aim of this episode to give direct language comparisons, I would compare constructor usage and syntax with Java. It can be controversial whether Scala gives an advantage here, what I think is the solutions we can use are straightforward and they let you see through the code easily.
Syntax-wise we have primary constructors, auxiliary constructors and we can use the apply method of the companion object. There are some limitations, though this can be treated as a simplification as well, regarding where we can put code that would execute some logic. The primary constructor, which resides right after the class name, cannot contain logic code, similarly to auxiliary constructors. We can add such code right in the body of the class, or in the apply methods in the companion object, these will be run on instantiation or method call respectively. I think it’s straightforward, and we can easily see where we have some code that will be run when we create a new class.
We earlier dived into some thoughts on what counts to be closer to pure Functional in terms of a hybrid like system we are provided by Scala, this circle of thoughts was introduced related to case classes, while we were mitigating between putting values and methods together or not, and considering case classes, the answer was sticking to exclusively group values into that block instead. As we have come to classes and companion objects, we will need to reassess, what should be considered a good practice, and why.
We know that from functional design basics, that we should avoid mutable values and prefer constants instead, but we are likely to have an object oriented example in mind like when an object is illustrated let’s say for example the object be a car, and let it have some state like its current speed, which would shout for a variable based implementation.
Suppose we want to stay closer to using constants everywhere, where it seems possible based on the specific developer’s mindset, what can we do then? First option is the case when we have a pure actor pattern implementation, where state is passed around in the form of messages, which can be case classes without any methods, just containing data. If we are not using this kind of implementation, what could be the guideline then and how does this work syntactically in Scala?
A Scala class has a primary constructor, which shall be implemented with constants to make our first step towards staying closer to pure Functional. We can have other constant values in the class body, which shall be initialized based on the values provided in the constructor, so that they are derived values. When coming to method implementations, we can mitigate which method do we need in the instance and what we put in the companion, as doing this is kind of equal to marking these as static in Java. This way, we should have lightweight class implementations, of which we can construct a new one once needed for a different state. So the Scala-ish way would be not to handle or minimize the amount of handled state in class instances, avoid it where we can.
To simplify that from another perspective, we have the term closures, which are basically functions that are not necessarily referentially transparent as they depend on at least one value in scope, that is not in their parameter list. The better if such a function depends on a constant, the worse if it depends on a variable. These shall be used with care and only if necessary, and such an allowed case could be classes having a non-empty primary constructor. So basically you can describe a well used Scala class as a set of closures bound to primary constructor constant values or constants in the class body being derived from these.
With Scala, we still have access modifiers, and the possibilities with traits were already mentioned, so we have the chance to break down logic and create huge OOP structures where we can expose the relevant interfaces to those parts of the project, where it is necessary to let them be seen.
Trying to wrap up the technicalities and computer language design characteristics we may find that flexibility can be a flaw of Scala considering object oriented programming, because contrary to the presence of the var keyword for a mutable variable which can be avoided as a rule of thumb in code, it is much more tempting to introduce a mutable field in a class as avoiding this would require further knowledge than having a simple rule in mind, and Scala does not enforce us avoiding non-functional practices in this case.
Seeking conclusions, we may also ask if Scala OOP enforces clean code if having some more flexibility we can make a clear statement on what should be considered Scala-ish clean code in specific cases. My personal answer would be: I believe it does due to the fact that we have pattern simplifications and traits as building blocks which help a lot, but it’s not as easy to intuitively figure out things compared to the case when patterns as language features were mentioned in Episode One, developers need to think more about what could be the right design.
Examining what value we get from Scala business-wise, we can easily come to the conclusion that it's quite a safe bet that Scala was designed with care to provide such values in mind, as we get something really easily readable for somebody with a Java or similar language background while we get a simple multiple inheritance-like feature and traits, which can also strengthen code ownership just as access modifiers do. In these terms we’re yet again well aligned with agile, just as when we checked out the cases of functional patterns in Episode Two. I can’t repeat often enough that this characteristic puts money on the table.
This chapter is about to end, I’ll be back with a new one on the 11th of October, 2021. This will be the first part of a double episode about architectural design challenges in Scala illustrated with the show’s pet project.
I know that this episode was more technical compared to the previous ones not having that much opportunity to focus on agile benefits, but it was a needed link in the chain of episodes as we may need to check back at this knowledge once going on with other topics.
Mind that this podcast has a LinkedIn group where you can meet great fellow tech people to discuss and stay tuned about the happenings related to this show.
I was Csaba Kincses, be back to you at the next episode; thanks for listening!