Cake Pattern in Scala / Self type annotations / Explicitly Typed Self References - explained
I've been trying to understand the cake pattern in Scala for some time, and until I didn't sit and write some code, it didn't click. Here is my attempt to explain it (to myself mostly) hope it might help others get a better understanding of this interesting pattern.
What is it for?
Dependency injection, "the scala way" (among other possible usages, but this post focuses on this one)
What is Dependency Injection?
The reverse of look-ups / named dependencies, e.g. if class X needs a database connection and gets it using
val con = DBConnectionRepository.getByName("appDBConnection")
then this is not dependency injection, it is coupled to the repository, and to the name (can't ever change it...)
Dependency injection tries to invert it, one simple way of doing so is by non optional constructor argument, another way is an abstract def. There are many other ways doing it (Spring, CDI, Guice) but this post will focus on a pattern called "the cake pattern"
What's with the "cake" anyway?
Good question, not sure what is the etymology, this comment is all I found
Why not use extends
?
So first thing, why do we need injection? why not just extend a trait we need to use or "inject"? since we can extend multiple traits, and each can have implementations, isn't that enough?
Well, let's see:
Let's assume we have a dependency:
trait FooAble {
def foo() = "here is your foo"
}
And something that uses it
class BarUsingFooAble extends FooAble {
def bar() = "bar calls foo: " + foo()
}
And client code
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble
println(barWithFoo.bar())
}
}
What is the problem? well, first, you are stuck with this specific FooAble
, if you want something that extends / implements FooAble
you need to modify the class or create another one, but this is not exactly dependency injection, the user of the dependency declares it specifically, it's not injected.
Why not use with
?
Why can't we use with
then? e.g.
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble with FooAble
println(barWithFoo.bar())
}
}
class BarUsingFooAble {
def bar() = "bar calls foo: " + foo()
}
Well, this of course doesn't compile, as BarUsingFooAble doesn't have a method foo
defined...
Why not use abstract methods then?
Dependency
abstract class BarUsingFooAble {
def bar() = "bar calls foo: " + foo.foo()
def foo:FooAble //abstract
}
object Main {
def main(args: Array[String]) {
val fooable = new FooAble {}
val barWithFoo = new BarUsingFooAble{
def foo: FooAble = fooable
}
println(barWithFoo.bar())
}
}
Well, it works, but don't you rather use mixins over implementing abstract methods? (although eventually abstract methods will be used in some way, but stay with me)
Self type annotations / Explicitly Typed Self References to the rescue
Here is where self type annotations come to help
class BarUsingFooAble {
this: FooAble => //see note #1
def bar() = "bar calls foo: " + foo() //see note #2
}
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble with FooAble //see note #3
println(barWithFoo.bar())
}
}
Explanation:
So what just happened here? what is this this: FooAble =>
thing? Well, it basically means that this class declares that it will eventually extend FooAble
somehow (e.g. via with FooAble
).
What's the difference from extending it? as said above, extends
is actually extending it and is very type specific. The self type annotation is just declaring that this type needs to extend / implement the annotated type, but it doesn't extend it yet. It lets you "inject" the extension, thus supports dependency injection.
More details:
1) you can use this
, self
or any identifier for the self type annotation see here for more information (answer by Martin Odersky himself on SO)
2) now BarUsingFooAble
asumes it was started with with FooAble
(or something that extends it)
3) if you don't use with FooAble
you'll get a compile error:
class BarUsingFooAble cannot be instantiated because it does not conform to its self-type BarUsingFooAble with FooAble
Multiple Implementations
Let's make FooAble
abstract to better illustrate the benefit of self type annotations over extension
trait FooAble {
def foo() : String
}
And have some concrete implementation
trait MyFooAble extends FooAble {
def foo() = "foo impl"
}
Now our client code won't compile because FooAble.foo
is abstract
It forces us to use an implementation (any implementation)
So changing it to with MyFooAble
(or any other implementation of FooAble) will work
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble with MyFooAble
println(barWithFoo.bar())
}
}
This is the greatness of dependency injection, BarUsingFooAble
depends on a FooAble
(any FooAble
implementation) and the client is forced to mix one in.
What about multiple injections?
Well, this is also possible, using with
e.g.
Let's add another dependency
trait BazAble{
def baz() = "baz too"
}
class BarUsingFooAble {
this: FooAble with BazAble =>
def bar() = s"bar calls foo: ${foo()} and baz: ${baz()}"
}
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble with MyFooAble with BazAble
println(barWithFoo.bar())
}
}
You can use this to "force" mix in of any number of dependencies this way
Why not use constructor params then?
Good question, let's assume these 2 dependencies
trait FooAble {
def foo() = "here is your foo"
}
trait BazAble{
def baz() = "baz too"
}
And something that uses the dependency, declaring it in the constructor
class BarUsingFooAble (dep:FooAble with BazAble) {
def bar() = s"bar calls foo: ${dep.foo()} and baz: ${dep.baz()}"
}
And some client code
object Main {
def main(args: Array[String]) {
val barWithFooAndBaz = new BarUsingFooAble(new FooAble with BazAble)
println(barWithFooAndBaz.bar())
}
}
Is it better / worse than using type annotations? I would say it's a matter of style and preference, couldn't find any deeper difference (please feel free to comment if you do)
Speaking of constructor params, why not use implicits?
There is a great thread on this in the scala-user Google group
Taking it further
now that we got self type annotations covered, let's see how it can be used for real world dependency injection
Wrap it in a component trait (abstract)
trait FooAbleComponent {
val fooAble: FooAble
class FooAble {
def foo() = "here is your foo"
}
}
trait BazAbleComponent {
val bazAble: BazAble
class BazAble {
def baz() = "baz too"
}
}
Depend on the components
class BarUsingFooAble {
this: FooAbleComponent with BazAbleComponent =>
def bar() = s"bar calls foo: ${fooAble.foo()} and baz: ${bazAble.baz()}"
}
Define the actual concrete implementation during injection time
object Main {
def main(args: Array[String]) {
val barWithFoo = new BarUsingFooAble with FooAbleComponent with BazAbleComponent {
val bazAble = new BazAble() //or any other implementation
val fooAble = new FooAble() //or any other implementation
}
println(barWithFoo.bar())
}
}
More on the rational above can be found in this excellent article
That's it, piece of cake...
Is it the best way to do injection in Scala? I personally prefer using CDI and @Inject if I'm in a Java EE / CDI container, but not always this is possible, and it's nice to know the alternatives!
If you think I missed something / wrote something incorrect / utterly stupid please feel free to correct / suggest / improve in the comments below
Written by Eran Medan
Related protips
19 Responses
Hi. I wanted to refer to the "Why not use extends" section. Cake can be actually used with "extends" instead of self type annotations. Precog folks do it. You just need to 1) inherit from a purely abstract trait and 2) mix in its concrete implementation using "with" later on.
Very clear and straight forward post , well done !!!
@przemekpokrywka, if I'm not mistaken, extending an abstract trait would compile only when implementing it's non defined methods (at least with scala 2.11).
@eranation, great article!
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@yoyomobli correct, but how will you do that is up to you: either you implement the abstract members directly or you mix in a trait, that already implements those.
@przemekpokrywka I guess what you meant was:
abstract trait A{definition}
trait B extends A{implementation}
trait B1 extends A{implementation}
trait B2 extends A{implementation}
class C{....}
val instance = new C with B/B1/B2...
In that case you're right but C cannot declare itself as extending A without implementing A's definitions - that wouldn't compile.
BTW, I found a disturbing limitation in class composing(using traits) in regards to structural typing:
Say class A declares a dependency in abstract trait B and then gets instantiated with trait B1 (which of course extends B) - B1's ctor will not be called until A's ctor isn't finished.
Although this behaviour is understandable(the trait isn't extended, it is mixed in an already constructed class instance), it might lead to unexpected results when defining default members in B1's ctor (which is a good practice for reasons of immutability) - those would be null until A is constructed meaning that any call to a B1 method which uses these members will result in some kind of a NullPointer exception.
A workaround to this problem would be to use expressions rather than vals, e.g:
trait B1 extends B{
val name: String = "Richard" //causes NullPointer
def name: String = "Richard" //will work
}
It's helped me. thanks a lot
Thanks. This is probably the clearest explanation of the Cake Pattern that I have read yet.
explained nicely