Question

I have an abstract base class (Base) that has some stacking traits defined for it (StackingTrait).

trait Base {
  def foo
}
trait StackingTrait extends Base {
  abstract override def foo { super.foo }
}

It would be very convenient to implement a subclass using the following syntax, but this doesn't work because the compiler says that foo needs to be declared with override and then with abstract override on recompile, which is invalid because Impl is a class.

class Impl extends Base with StackingTrait {
  def foo {}
}

I cannot think of a good reason why such syntax would be disallowed; foo is logically defined with Impl so that ordering that stacking occurs in conceptually remains the same.

Note: I figured out this workaround that will effectively do the same thing that I want, but the necessity of a helper class makes me want a better solution.

class ImplHelper extends Base {
  def foo {}
}
class Impl extends ImplHelper with StackingTrait

Why does the desired syntax not compile and is there an elegant solution?

Was it helpful?

Solution

My understanding is that while the error message may be confusing, the behaviour is correct. foo is declared as abstract override in StackingTrait, and thus in any concrete class that mixes StackingTrait there must be a concrete (not marked as abstract) implementation of foo before StackingTrait (relative to the linearization order). This is because super refers to the trait just before in the linearization order, so there definitely needs to be a concrete implementation of foo before StackingTrait is mixed in, or super.foo would be nonsensical.

When you do this:

class Impl extends Base with StackingTrait {
  def foo {}
}

the linearization order is Base <- StackingTrait <- Impl. The only trait before StackingTrait is Base and Base does not define a concrete implementation of foo.

But when you do this:

traitImplHelper extends Base {
  def foo {}
}
class Impl extends ImplHelper with StackingTrait

The linearization order becomes: Base <- ImplHelper <- StackingTrait <- Impl Here ImplHelper contains a concrete definition of foo, and is definitly before StackingTrait.

For what is worth, if you had mixed ImplHelper after StackingTrait (as in class Impl extends StackingTrait with ImplHelper) you would again have the same problem and it would fail to compile.

So, this look fairly consistent to me. I am not aware of a way to make it compile as you intended to. However if you are more concerned about making it easier to write Impl (and being able to define foo right there without a need for a separate class/trait) than making it easy to write Base or StackingTrait, you can still do this:

trait Base {
  protected def fooImpl
  def foo { fooImpl } 
}
trait StackingTrait extends Base {
  abstract override def foo { super.foo }
}

class Impl extends Base with StackingTrait {
  protected def fooImpl {}
}

Just like in the original version you force each concrete class to implement foo (in the form of fooImpl) and this time it does compile. The downside here is that while fooImpl must not call super.foo (it makes no sense and will go into an infinite loop), the compiler won't warn you about it.

OTHER TIPS

Instead of extending the trait you can try to use a self-type as mentioned in the example. https://docs.scala-lang.org/tour/self-types.html.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top