Scala Context Bounds as 'Has-A' Relationship
How to understand the meaning of context bounds as a relationship
Context bounds is a well-known Scala construct. It can simplify your code, help constructing type classes or obtaining a TypeTag. But how can it be easily read and understood while reading the code?
Basically, context bounds can be seen as a shorthand notation for an additional implicit parameter. Thus the following definitions are equivalent:
def f[A : B](a: A) = ???
def f[A](a: A)(implicit b: B[A]) = ???
But what it really means to define a type with context bounds, how it can be easily understand? Let’s start with an example:
case class Planet(name: String)
trait Destroyer[T] {
def destroy(t: T)
}
We’ve defined a case class Planet
and a type-parametrized trait Destroyer
.
How about destroying a whole planet? 😈 We will need a powerful wepon.
implicit val deathStar: Destroyer[Planet] = (p: Planet) => println(s"${p}: BOOOM!")
Here we go! And now we would like to use the wepon, in fact we could make a
method for using any available Destroyer
to destroy specific type of objects:
def useDeadlyWepon[A : Destroyer](something: A) = {
implicitly[Destroyer[A]].destroy(something)
}
And what we got here is a method that accepts any object of type A
which has a Destroyer
or we could say, type A
for which there is a
Destroyer
, meaning Destroyer[A]
. Using this kind of understanding of context
bounds, we can more easily read the source code.
In this example, implicitly looks in the scope for an explicit value with
the specified type, hence invoking the useDeadlyWepon
method with a parameter
of type Planet
will use the deathStar
internally:
useDeadlyWepon(Planet("Alderaan"))
// Planet(Alderaan): BOOOM!
Context bounds can be combined with lower / uper bounds, e.g. let’s make a method
for sorting animals, it can accept any Animal
type, which has or
for which there is an Ordering
defined.
trait Animal {
val name: String
}
implicit val byName: Ordering[Animal] = (x: Animal, y: Animal) => x.name.compare(y.name)
def sortAnimals[A <: Animal: Ordering](as: Seq[A]): Seq[A] = as.sorted
Quite intuitive, isn’t it?
I’ve found the idea in this great comment on StackOverflow and I wanted to share it, since reading it was one of my aha-moments, which greatly simplified the way I am able to read Scala code.