Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: Trait Mixin with Abstract Base Class

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?

like image 689
sient Avatar asked Jan 05 '13 07:01

sient


People also ask

Can mixin be abstract?

All mixins start with a generic constructor to pass the T through, now these can be abstract.

What is trait mixing in Scala?

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.

What is the difference between abstract class and trait in Scala?

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.

What is the difference between a trait and a mixin?

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.


1 Answers

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.

like image 141
Régis Jean-Gilles Avatar answered Sep 21 '22 15:09

Régis Jean-Gilles