6

I'm currently currently using the Cake Pattern to implement some optimization algorithms. I often hit name collision problems. For instance:

trait Add[T] { this: Foo[T] =>
  def constant: T
  def plus( t1: T, t2: T ): T
  def add( t: T ) = plus( t, constant )
}

trait Mul[T] { this: Bar[T] =>
  def constant: T
  def times( t1: T, t2: T ): T
  def mul( t: T ) = times( t, constant )
}

trait Operations[T] { this: Add[T] with Mul[T] =>
  def neg( t: T ): T
}

Here, constant is defined in both Add and Mul traits, but their values could be different. I could prefix the name with the trait name but I find it ugly and brittle (def mulConstant: T). Is there a better way of doing it ?

paradigmatic
  • 40,153
  • 18
  • 88
  • 147

2 Answers2

7

To my best knowledge, the traditional cake pattern usually involves 1 layer of trait nesting, to group operations together. Then, the outer layer declares the actual "service" (here: Add, Mul, Operations) without defining it.

trait AddComponent[T] { this: FooComponent[T] =>
  def addition: Add

  trait Add {
    def constant: T
    def plus( t1: T, t2: T ): T
    def add( t: T ) = plus( t, constant )
  }
}

trait MulComponent[T] { this: BarComponent[T] =>
  def multiplication: Mul

  trait Mul {
    def constant: T
    def times( t1: T, t2: T ): T
    def mul( t: T ) = times( t, constant )
  }
}

trait OperationsComponent[T] { this: Add[T] with Mul[T] =>
  def operations: Operations

  trait Operations {
    def neg( t: T ): T
  }
}

Then, when mixing the "...Component" traits together, the dependencies are wired:

trait IntOperations extends Operation[Int] {
  class IntAdd extends Add { ... }
  class IntMul extends Mul { ... }
}

class MyFooBar extends FooComponent[Int] with BarComponent[Int] with IntOperations {
  lazy val addition = new IntAdd
  lazy val multiplication = new IntMul
  lazy val foo = ...
  lazy val bar = ...
}

This solves your particular namespacing problem but name clashes (of "service" definitions) remain a problem of the traditional cake pattern. There is a blog post by Daniel Spiewak demonstrating how that can be solved in general but the solution comes with its own set of (huge) tradeoffs (see this talk).

Hope that helped a bit.

P.S. instead of type parameters it might be better to use abstract types here

  • Thanks for your answer, it solves my problem indeed. Regarding your PS, I will also have name type clashes if I define the abstract type `T` in each trait (since it should be defined at the "component" level). Am I wrong ? – paradigmatic May 14 '13 at 09:43
  • In this particular case, `T` means the same "thing" in all of the traits, so they won't clash. Even if each trait contributes a T with a different type bound they won't clash, their type bounds are rather collected together from the different traits when they're mixed together. As long as they bounds don't clash, things are fine (for the compiler). That's one thing that Daniel Spiewak shows in that blog post. Nevertheless, I'd personally give an abstract type on the "component" level a more descriptive name than `T`, since 2 `T`s don't necessarily mean the same thing in 2 different components. –  May 14 '13 at 10:02
  • OK. But here I need to be sure that types are unified (`type T = Int` everywhere), so I need to express the dependency that all traits mixed have the same `T`. I don't see how to achieve that except with generic types. – paradigmatic May 14 '13 at 11:04
  • Hmm, maybe this gist clears this up a bit? https://gist.github.com/anonymous/5575228 –  May 14 '13 at 11:19
  • Also, this particular problem is probably better expressed by using typeclasses instead of inheritance. See e.g. Scala's `Numeric` typeclass or Spire (https://github.com/non/spire). –  May 14 '13 at 11:22
  • Thanks for all. The problem here is not the real one I am coding and I fully agree with you about typeclasses. – paradigmatic May 14 '13 at 12:33
1

This may be a bit unfashionable to say, and it's not a direct answer to your question, but isn't it easier to simply do dependency injection into constructors? Each collaborator has its own namespace so there's never a clash. And there's no problem either with the public api of the class. However, it remains hard to intermix the DI and cake patterns.

Rick-777
  • 9,714
  • 5
  • 34
  • 50