gzm0
gzm0

Reputation: 14842

Override abstract type member in stackable trait pattern

Suppose we want to define ways how to accumulate results over some data:

case class Data(x: Int, y: Int)

We define a trait to do so:

trait Accumulator {
  type R   
  def add(acc: R, d: Data): R
  def zero: R
}

And a simple implementation:

trait XAccumulator extends Accumulator {
  type R = Int
  def add(acc: Int, d: Data) = acc + d.x
  def zero = 0
}

I would like to use the stackable-trait pattern to use multiple of these simple accumulators:

trait TraceYAccumulator extends Accumulator {
  abstract override type R = (Seq[Int], super.R)
  // fails:
  // `abstract override' modifier not allowed for type members

  def add(acc: R, d: Data) = {
    val r = super.add(acc._2, d)
    (acc._1 :+ d.y, r)
  }

  def zero = (Seq.empty[Int], super.zero)
}

Apparently I am not allowed to override an abstract type member. How can I alter the result type of the overridden methods using the stackable trait pattern?

My second approach was to use type parameters:

trait Accumulator[R] {
  def add(acc: R, d: Data): R
  def zero: R
}

trait XAccumulator extends Accumulator[Int] {
  def add(acc: Int, d: Data) = acc + d.x
  def zero = 0
}

But now it becomes really strange:

trait TraceYAccumulator[T] extends Accumulator[(Seq[Int], T)] {
  this: Accumulator[T] =>

  def add(acc: (Seq[Int], T), d: Data): (Seq[Int], T) = {
    val r = this.add(acc._2, d)
    // fails: overloaded method value add with alternatives:
    // (acc: (Seq[Int], T),d: Data)(Seq[Int], T) <and>
    // (acc: _14695,d: Data)_14695 cannot be applied to (T, Data)
    (acc._1 :+ d.y, r)
  }
  def zero: (Seq[Int], T) = (Seq.empty[Int], this.zero)
}

Since the super-class and the mixed-in class have the same method names (obviously), we cannot refer to the correct methods. Is my only option to use composition?

How can I stack such kind of operations?

Upvotes: 4

Views: 859

Answers (1)

gzm0
gzm0

Reputation: 14842

The first approach has a fundamental theoretical problem:

Suppose we are allowed to write these traits. Then consider the following:

val acc = new XAccumulator with TraceYAccumulator
val xacc = acc: XAccumulator

Since xacc is an XAccumulator, we know that x.R =:= Int. Ooops.

The second approach has an implementation problem: You cannot inherit from different instances of the same trait.

illegal inheritance; anonymous class $anon inherits different type instances of trait Accumulator: Accumulator[Int] and Accumulator[(Seq[Int], Int)]

Therefore, composition (or maybe some typeclass hackery) seems to be the only option.

Upvotes: 1

Related Questions