Using cake and stackable traits patterns together

I want to ask profies whether I can play around simultaneous application of cake pattern and stackable trait pattern?

Here is an example of the classical stackable traits pattern. I can use some FunnyModifiers to change somehow behaviour in known way (for instance, for implementing aspect-oriented approach).

trait GeneralTrait {
  def f: Int

class BaseImpl extends GeneralTrait {
  override def f: Int = 5

class BaseImpl2 extends GeneralTrait {
  override def f: Int = 50

trait FunnyModifier extends GeneralTrait {
  abstract override def f: Int = super.f + 6

These definitions implies the following statements to be true:

(new BaseImpl).f == 5
(new BaseImpl with FunnyModifier).f == 11
(new BaseImpl2 with FunnyModifier).f == 56

But sometimes it can be desired to go with the cake pattern too (e.g. when no common trait exists for given bunch of implementations, only concrete class structure), i.e. to write something like

class BaseImpl {
  def f: Int = 5

class MoreImpl extends BaseImpl {
  def u: String = "whatever functionality else"

trait FunnySubstitutor { self: BaseImpl =>
  abstract override def f: Int = 6

This implementation actually works since FunnySubstitutor fully substitutes the original function. But what if I want to modify the original behaviour, not only substitute? How can I address to actual parent’s implementation (i.e. an implementation of a class modifier (i.e. cake piece) is mixed with)?

trait BadFunnyModifier1 { self: BaseImpl =>
  abstract override def f: Int = parent.f + 6 // Does not compile, parent has type AnyRef.

trait BadFunnyModifier2 { self: BaseImpl =>
  abstract override def f: Int = parent[BaseImpl].f + 6 // Does not compile, parent is not BaseImpl yet.

trait BadFunnyModifier3 { self: BaseImpl =>
  abstract override def f: Int = self.f + 6 // Compiles, but self.f call is virtual causing stack overflow.

For instance, why parent cannot be thought as an inheritant of BaseImpl in this case (so, making first two BadFunnyModifiers to work)? This information is known at the compile time, as far as I can see.

So, is it possible somehow to make two wonderful patterns work together? I think it can be extremely useful for aspect-oriented-like addition of (originally) irrelevant functionality to the original functionality.

That can appear surprising, but nothing prevent you from making the trait actually extend the class:

trait FunnySubstitutor extends BaseImpl {
  abstract override def f: Int = super.f + 1

Then, (new MoreImpl with FunnySubstitutor).f will return 6, as expected.

Note that this also work even if BaseImpl takes parameters (though the extends clause in the trait cannot pass them and will look the same as above).

1 Like

Am I right that

trait SomeTrait extends SomeClass {
  // whatever

means in fact the same as

trait SomeTrait { this: SomeClass =>
  // whatever


  • it can use super in its body;
  • it looks weird

? Are there any more cases of difference?

Yes, there is another difference: you cannot extend the trait with the self-type with explicitly writing extends SomeClass yourself, whereas that is automatic for the one that extends SomeClass:

scala> class SomeClass
defined class SomeClass

scala> trait SomeTrait extends SomeClass
defined trait SomeTrait

scala> new SomeTrait {}
res0: SomeClass with SomeTrait = $anon$1@383534aa

scala> trait SomeTrait2 { this: SomeClass => }
defined trait SomeTrait2

scala> new SomeTrait2 {}
<console>:10: error: illegal inheritance;
 self-type SomeTrait2 does not conform to SomeTrait2's selftype SomeTrait2 with SomeClass
              new SomeTrait2 {}

Yet another difference is that with a self type, the SomeClass bound is only visible within the body of SomeTrait2, but not from the outside:

scala> implicitly[SomeTrait <:< SomeClass]
res0: <:<[SomeTrait,SomeClass] = <function1>

scala> implicitly[SomeTrait2 <:< SomeClass]
<console>:14: error: Cannot prove that SomeTrait2 <:< SomeClass.
       implicitly[SomeTrait2 <:< SomeClass]