The Cake Pattern in Scala - Self Type Annotations vs. Inheritance

August 7, 2014 | Tags: scala | Discuss: HackerNews

I'm a fan of the Cake Pattern for managing dependencies in Scala code. However, for many people, the Cake Pattern is often the first time they see self type annotations. Upon seeing them, a natural question is often, "why use self type annotations instead of just using inheritance?"

In other words, what is the point of doing this:

trait A trait B { this: A => }

when you could instead just do this:

trait A trait B extends A

It's a valid question if you've never used self type annotations. Why should you use them?

Some have answered this question on Stack Overflow, and the answer generally comes down to "B requiring A" (annotations) vs "B being an A" (inheritance) and that the former is better for dependency management. However, there isn't much of an explanation as to why - we're still left hanging. That leaves us with the question: why does that difference matter in practice?

The best hint is in this comment:

The practical problem is that using subclassing will leak functionality into your interface that you don't intend.

Huh? Leak how? I want some code to show me.

Look at what happens in this example using self type annotations:

trait A { def foo = "foo" } trait B { this: A => def foobar = foo + "bar" } // !!! This next line throws a "not found: value foo" compilation error trait C { this: B => def fooNope = foo + "Nope" }

A has method foo. B requires A. C requires B. But C can't call methods in A! If I were to do the same thing with inheritance:

trait A { def foo = "foo" } trait B extends A { def foobar = foo + "bar" } // This next line works trait C extends B { def fooYep = foo + "Yep" }

So C can call foo from A. Now we start to see what we mean by "requiring an A" vs "being an A" and what it means to "leak functionality".

But why is it bad if C can call methods from A? That's just normal inheritance. What is so special about the Cake Pattern that we care about hiding A from C? To answer that, let's consider what A, B, and C could be in a real Cake Pattern scenario.

Suppose we are building a typical web app.

"A" is a database interface.

"B" is an abstraction on the database interface to just manipulate user information.

"C" is a service that sends emails to users based on their username. It looks up the user's email address from their username via the user abstraction that B provides.

With inheritance it looks something like this:

trait Database { // ... } trait UserDb extends Database { // ... } trait EmailService extends UserDb { // Ends up getting access to all Database methods // when it really should just be able to talk to the UserDb abstraction! }

If we instead used self type annotations:

trait Database { // ... } trait UserDb { this: Database => // ... } trait EmailService { this: UserDb => // Can only access UserDb methods, cannot touch Database methods }

So we've hidden the full Database functionality from EmailService. This was a fairly simple example, but Database or UserDb could have required many other components in practice. We've avoided all of it being exposed to EmailService by using self type annotations instead of inheritance. That's probably a good thing when doing dependency management, right?

There's a famous quote in the book Design Patterns: Elements of Reusable Object-Oriented Software which says:

Favor 'object composition' over 'class inheritance'. (Gang of Four 1995:20)

Further explanation is given in the form of the following:

Because inheritance exposes a subclass to details of its parent's implementation, it's often said that 'inheritance breaks encapsulation'. (Gang of Four 1995:19)

There are other hidden benefits with self-type annotations, too. My colleage at Localytics, Dan Billings, reminded me that you can have cyclical references with self type annotations and you can't with inheritance - at least in Scala. In other words, you can do this:

trait A { this: B =>} trait B { this: A =>}

This might make sense in some settings.

UPDATE (Aug 9, 2014): It's worth considering composition via constructors or members as another possibility. We could do the following:

class A { def foo = 1 } class B ( val a : A ) class C ( val b : B )

Or this:

trait A { def foo = 1 } trait B { def a : A } trait C { def b : B }

In these cases, however, whatever ultimately ends up using or extending C will have access to b.a.foo .

At the end of the day, you can probably get away with using inheritance and notice no difference in practice, but sometimes that extra bit of compiler enforced separation can keep your code neatly compartmentalized.

I'll leave you with a fun quote:

All problems in computer science can be solved by another level of indirection, except of course for the problem of too many indirections. (said by David Wheeler, brought to my attention by Jon Bass)

Further reading: