Avoid name collision with Cake Pattern
Asked Answered
K

2

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 ?

Kareykari answered 13/5, 2013 at 10:19 Comment(0)
C
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

Colorful answered 13/5, 2013 at 10:41 Comment(6)
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 ?Kareykari
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 Ts don't necessarily mean the same thing in 2 different components.Colorful
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.Kareykari
Hmm, maybe this gist clears this up a bit? gist.github.com/anonymous/5575228Colorful
Also, this particular problem is probably better expressed by using typeclasses instead of inheritance. See e.g. Scala's Numeric typeclass or Spire (github.com/non/spire).Colorful
Thanks for all. The problem here is not the real one I am coding and I fully agree with you about typeclasses.Kareykari
S
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.

Steib answered 14/5, 2013 at 17:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.