Search code examples
scalatraitscake-patternname-clash

Avoid name collision with Cake Pattern


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 ?


Solution

  • 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