This will be a small post where I share some techniques we found for getting some additional type safety from our Scala case classes. We’ll look at case classes that validate their input data and we look at case classes that allow selecting from among multiple instances of the same typeclass.
Validating a Subset of a Type
In our current project, we have several
AnyVal case classes that
represent some of our domain objects. Each is backed by a string that
must match a certain regex in order to make sense in our domain model.
In that sense, each of our domain case classes represents a different
subset of the type
String. To make things a bit simpler in this blog
post, we’ll consider the analogous situation where we need a subset of
Double, but all the same techniques still apply.
Let’s start by considering an obviously-broken (judging by its type
squareRoot function, and let’s make things interesting by
NaN: Double doesn’t exist, so now we have to decide
what happens when the input is negative.
We have a few options. One thing we can do is go ahead and perform our operation on the raw bits, passing back corrupt data to the caller. A better alternative is to raise an exception, a perfectly fine thing to do in a dynamically-typed language. But Scala is statically typed, so we can do better.
One way to fix this function is to make the range—the set of
potential outputs—larger, by adding a special value to represent
failure. This is exactly what returning an
Option[Double] would do for
us, but that’s just one way to fix this function. The other way to fix
this function is by making the domain—the set of legal
Option approach, the
NonNegative approach has the
advantage of separating validation and business logic. The idea is that
if we are passed a
NonNegative, we can be confident that validation
has already occurred at some earlier point. This brings us to our main
point: how do we implement
NonNegative so that whenever we have one we
can be confident that validation has already occurred?
We chose to use case classes for their brevity and their equality
semantics. For runtime optimization, we extend
Of course, case classes by default have no validation. We can implement
validation by creating our own
apply method in the companion object,
instead of relying on the
apply method the compiler would have
generated for us (this works in Scala 2.12 and above).
We still have to harden our case class against alternate creation paths: make the case class final and make the constructor private.
There’s still one more backdoor that we haven’t boarded up. Take a look:
NonNegative#copy method calls the
constructor (instead of
NonNegative.apply), skipping our validation.
We patch this leak up by providing our own
instead of relying on the default, compiler-generated
This is, as far as I know, the last leak in our encapsulation (if you can think of another leak, please comment below). Using this light-weight pattern, we’re able to push all of our input validation to the edges of our app, allowing our business logic to concentrate on managing and operating on validated data.
Selecting from Multiple Typeclass Instances
A similar problem we had was supporting multiple typeclass instances for a single case class. Imagine a case class with hundreds of fields which represents two distinct domain objects, but is implemented as a single case class in order to avoid code duplication and accidental drift.
For simplicity, we’ll consider a case class with just two fields, but imagine hundreds.
Again, we want to use one case class to avoid error-prone duplication
that we’d need to remember to keep synchronized manually. At the same
time, we need to select different instances of
different customers. We ended up using mix-in traits to select the
correct typeclass instance.
Below is our full solution. Notice in particular the traits
PremiumView, the implicit class
Views, and the instances
The class casts in
Views are safe because all creation is funneled
Content.apply, which secretly imbues the returned
PremiumView mix-ins. When we want to pass a
Content value to a
Renderable method, we first take the appropriate
This pattern has a heavy footprint in terms of boilerplate. Each field
has to be stated five times: once in the case class definition, twice in
copy method, and twice in the
apply method. Still, we consider
this to be better than the alternative of simply copying the
Content into two distinct case classes and keeping
them synchronized manually.
Notably, we could have avoid all of this nonsense by simply writing
renderFreeView(x: Content): HTML and
renderPremiumView(x: Content): HTML, and by simply calling them
explicitly instead of relying on typeclasses and implicit resolution;
however, a library we were using required us to provide these
typeclass instances, creating our need to write
multiple instances for the same underlying case class. While typeclasses
can be convenient and can reduce repetition, it’s usually also
worthwhile for library authors to expose non-overloaded versions of
their functions to prevent forcing clients into the situation the above
pattern exists to solve.