Last Updated: September 15, 2020
·
47.48K
· eranation

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

19 Responses
Add your response

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.

over 1 year ago ·

Very clear and straight forward post , well done !!!

over 1 year ago ·

@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!

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

@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.

over 1 year ago ·

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
}

over 1 year ago ·

It's helped me. thanks a lot

over 1 year ago ·

Thanks. This is probably the clearest explanation of the Cake Pattern that I have read yet.

over 1 year ago ·

explained nicely

over 1 year ago ·