scalacake-pattern

What are the benefits of the cake approach under old fashioned trait extending?


I'm trying to find out what is the difference between mixing traits via Cake pattern and mixing them via old fashioned extending. Here are my two examples:

Via extending

trait X {
  def foo()
}

trait Y extends X {
  def bar()
}

class Z extends Y {
  def foo() = ()
  def bar() = ()
}

And via Cake

trait N {
  def foo()
}

trait M {
  this: N =>
  def bar()
}

class U extends M with N {
  def bar() = ()
  def foo() = ()
}

What are the benefits of the cake approach? They are both the same for me. Maybe I'm wrong, but I can't see any significant difference.


Solution

  • If I want to enrich the functionalities of X, the first approach:

    // I am X
    // I give you everything X provides
    trait Y extends X {
      def bar()
    }
    

    If I want to use functionality of N, I would use the second approach:

    // I am an extension of N
    // I require you to be N
    trait M { this: N =>
      def bar()
    }
    

    The main benefit of "the cake approach" over the "old fashion" is not setting the hierarchy constraint upfront and avoiding (eventually) the diamond problem by using the "trait linearization".

    The traits forming the class U implemented in your example get resolved from right to left. Having a sane Method Resolution Order (MRO) gives us the freedom of mixing any trait in a "stackable fashion".