Have you seen a type signature like this before?
If you’re like me, you’ve come across this type signature, and you’re wondering what the heck it means. You likely Googled something like “recursive type” or “self-referential type” and ended up here.
So what does this type signature mean? Why not use trait
T[U]
?
To understand the meaning of T[U
<: T[U]]
, we’ll work through a simple example.
Example
Suppose we want database entities with CRUD methods. We could define them like this:
But we can see that these classes look nearly identical. In addition, any new CRUD entities we add will expose the same CRUD methods. Now, should we abstract the interface into a trait? Let’s see what happens when we do:
Well this sucks. Our method signatures don’t fully express what we want. We’ve lost the ability to ensure that e.g. calling update
on
an Apple
returns
an Apple
.
As is, it can return any CrudEntity
.
Let’s try to regain some specificity by adding a type parameter to our CrudEntity
trait:
Okay, better. But we still haven’t locked this down. Our types don’t yet express exactly what we want. Do you see the problem?
The problem is that someone can extend CrudEntity_2
in
a way we didn’t intend them to:
Whoa! In the code above, CrudEntity_2[E]
does
not restrict the type of E
,
so they can use anything they want, without complaint from the compiler — FloobyDust, Potato, BurritoAstronaut, you name it.
This is no bueno. Instead, we’d like them to get a big, fat compiler error if they try extending anything other than CrudEntity_2[Orange]
.
How do we ensure that E
matches
the class we’re defining?
Let’s try defining CrudEntity
again.
This time, we’ll use type bounds:
Better. Now we’ve constrained E
to
be a subtype of CrudEntity
.
No more FloobyDust. But there’s one last problem, and you can probably guess what it is. We haven’t yet ensured that E
matches
our class type, only that it subclasses CrudEntity
. CrudEntity
is
still open for abuse:
Yuck! To take care of this, we need a way to ensure that e.g. Orange
extends CrudEntity_3[Orange]
.
For this assurance, we’ll use a self type.
Here is our final definition of CrudEntity
,
which uses a self type:
self:
E =>
ensures that any concrete class that extends CrudEntity
must
be of type E
and
that code like
will get rejected by the compiler because Orange
is
not of type Apple
.
Now we can rest, confident that our definition of CrudEntity
ensures
that any subtype of CrudEntity[E]
must
in fact be an E
.
This definition gives us the semantics we desire, and enlists the compiler to reject all code in violation.
http://blog.originate.com/blog/2014/02/27/types-inside-types-in-scala/