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?
All mixins start with a generic constructor to pass the T through, now these can be abstract.
In scala, trait mixins means you can extend any number of traits with a class or abstract class. You can extend only traits or combination of traits and class or traits and abstract class. It is necessary to maintain order of mixins otherwise compiler throws an error.
Abstract Class supports single inheritance only. Trait can be added to an object instance. Abstract class cannot be added to an object instance. Trait cannot have parameters in its constructors.
Traits are compile-time external values (rather than code generated from an external source). The difference is subtle. Mixins add logic, Traits add data such as compile-time type information.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With