Yan Cui
I help clients go faster for less using serverless technologies.
This article is brought to you by
Don’t reinvent the patterns. Catalyst gives you consistent APIs for messaging, data, and workflow with key microservice patterns like circuit-breakers and retries for free.
Read the whole series:
Part 1 — type inference
Part 2 — traits <- you’re here
Part 3 — case class/object (ADTs)
Part 4 — apply & unapply functions
Part 5 — implicits
Continuing on from where we left off with type inference last time around, let’s look at a language feature in Scala that doesn’t exist in F# – traits.
Scala has both abstract classes and traits (think of them as interfaces, but we’ll get into the differences shortly) to support OOP. Abstract classes are exactly what you’d expect and the preferred option where Java-interop is concerned. Traits, however, are much more flexible and powerful, but with great power comes great responsibility.
Basics
Like abstract classes, they can contain both fields and behaviour, and both abstract definitions and concrete implementations.
Any class that extends from this trait will inherit all the concrete implementations and need to implement the abstract members.
Of course, the concrete class can also override the default implementation that came with the trait.
You can extend multiple traits.
wait, hold on a sec, what’s this object thingymajig?
Sorry for springing that on you! A Scala object is basically a singleton and is Scala’s equivalent to a F# module. In fact, when you define an object in the Scala REPL it actually says “defined module“!
The notable difference with a F# module is that a Scala object can extend abstract classes and/or traits (but itself cannot be extended by another class/object). We’ll spend more time drilling into object later in the series but I just wanted to throw it in here for now as it’s such a heavily used feature.
The key thing to take away from the snippet above is that you can extend a class or object with multiple traits.
Traits vs Abstract Classes
There are 2 differences between traits and abstract classes:
- traits cannot have contractor parameters but abstract classes can
- you can extend multiple traits but can only extend one abstract class
With regards to point 2, you can actually mix both traits and abstract classes together!
Dealing with Collisions
Since you can extend more than one thing at a time (be it multiple traits, or 1 abstract class + 1 or more traits), one must wonder what happens when some of the members collide.
You might have noticed from our last snippet that both Being and Human defines a name field, but everything still works as expected and you can indeed use the theburningmonk object as a Human or Being.
Ok. What if I extend from 2 traits with clashing members and one of them provides a concrete implementation? My expectation would be for the concrete implementation to fill in for the abstract member with the same name.
and indeed it does.
What if I’m mixing traits with an abstract class?
No problem, still works.
side note: notice that in this second version ProfessorX is extending Psychic first? That’s because in cases where abstract class and traits are both involved, only the traits can be mixed in.
So far so good, but what if both traits/abstract classes provide a concrete implementation for a clashed member?
The safe thing to do here would be for the compiler to crap out and force the developer to rethink what he’s doing rather than springing a nasty surprise later on.
(and yes, it behalves the same way if Light or Dark is an abstract class instead)
This, in my opinion is a much better way to resolve collisions than the Python way.
Dynamic Composition
So far we have mixed in traits when defining a new class or object, but you can do the same in a more ad-hoc fashion.
Of course, all the rules around collision resolution also hold true here.
Generics
Whilst traits cannot have constructor parameters, they can have type parameters (ie, they are generic!).
To sneakily weave in the Transformers game I’m working on at Space Ape Games, here’s how you might model a Transformer Combiner as a generic trait.
Stackable Modifications
When you read about traits in Scala, you often hear the phrase “stackable modifications” (Jonas Boner’s real-world Scala slide deck has a nice example).
What makes this interesting (and different from straight up override in inheritance) is how super is modified in an ad-hoc fashion as you compose an object using traits (see Dynamic Composition section above).
This also works if Mutant is an abstract class, and in the definition of an object too.
However, it only work with methods. If you try to override an immutable value then you’ll get a compiler error.
And no, it doesn’t work with variable either, only methods.
self type annotation
Another related topic is the so-called self type annotation feature. It is often used to implement dependency injection in Scala with patterns such as the Cake Pattern.
Where you see code such as below (note the self : A => ), it means the trait B requires A.
Any composing object/class would need to mix in trait A if they want to extend trait B, failure to do so will be met with a swift and deadly compiler error.
This also gives the trait B access to members from A (which includes any members A inherits). For instance.
What’s more, you can mix in multiple traits in the self type.
It’s worth differentiating this “requires” relationship from the “is a” relationship created through inheritance.
In this case, since the Rooney trait is not a Footballer and RecordHolder (which he is in real life, of course) it won’t inherit the members from those traits either.
Interestingly, it’s possible to create cyclic dependencies using self type
which is not possible through inheritance.
As a .Net developer who have seen the damages cyclic references can do, I’m slightly concerned that you could do this in Scala…
That said, in Martin Odersky‘s Programming in Scala, he has an example of how this mutual dependency can be useful in building a spreadsheet, so I’ll keep an open mind for now.
…a new trait, Evaluator. The method needs to access the cells field in class Model to find out about the current values of cells that are referenced in a formula. On the other hand, the Model class needs to call evaluate. Hence, there’s a mutual dependency between the Model and the Evaluator. A good way to express such mutual dependencies between classes was shown in Chapter 27: you use inheritance in one direction and self types in the other.
In the spreadsheet example, class Model inherits from Evaluator and thus gains access to its evaluation method. To go the other way, class Evaluator defines its self type to be Model, like this:
package org.stairwaybook.scells trait Evaluator { this: Model => ...
Finally, as you can see from Martin Odersky‘s example above, you don’t have to use “self” as the name for the self instance.
side note: I’m curious as to how Scala deals with a cyclic dependency where the traits define values that depends on a value on the other trait.
as you can see from all that red, even the code analysis tool in IntelliJ gave up trying to understand what’s going on, but it compiles!
What’s interesting (and again, slightly worrying) is when the fields are evaluated, which actually depends on the order the traits are mixed in.
(new Object with Yin with Yang).motto
- Yin is mixed in, yang.motto is not yet initialised (and therefore null) so Yin.phrase is initialised as null + “yin”
- Yang is mixed in, yin.phrase is initialised to “nullyin“, so Yang.motto is now “nullyin” + “yang”
- the value of motto is therefore nullyinyang
(new Object with Yang with Yin).motto
- Yang is mixed in, yin.phrase is not yet initialised, so Yang.motto is initialised as null + “yang”
- Yin is mixed in, but since motto is already initialised, whatever happens here doesn’t really affect our result
- the value of motto is therefore nullyang
ps. please DO NOT try this at work!
So, that’s everything I have learnt about traits in the last week, hope you have found it useful in your path to learn Scala.
Until next time, ciao!
Links
- Scala School : traits
- Effective Scala : traits
- SO: What’s the advantage of using abstract classes instead of traits?
- Programming in Scala, First Edition, Chapter 12: Traits
- Programming in Scala, First Edition, Chapter 33
- SO: What’s the difference between self-types and trait subclasses?
- Scala: Understanding the self type annotation and how it relates to the Cake Pattern
- Cake Pattern in Scala
- SO: What are stackable modifications?
- Stackable modification using traits in Scala
- SlideShare: Pragmatic Real-World Scala
- Slides: Detangling Software Dependency Networks
Whenever you’re ready, here are 3 ways I can help you:
- Production-Ready Serverless: Join 20+ AWS Heroes & Community Builders and 1000+ other students in levelling up your serverless game. This is your one-stop shop for quickly levelling up your serverless skills.
- I help clients launch product ideas, improve their development processes and upskill their teams. If you’d like to work together, then let’s get in touch.
- Join my community on Discord, ask questions, and join the discussion on all things AWS and Serverless.