Hello,
I am just learning Scala, but have experience in several other languages. Reading "Programming in Scala 3rd edition", I am a bit confused about trait inheritance. Though the author claims trait inheritance are not multiple inheritance, he may be right when he has C++ in his mind, but compared to languages like Common Lisp or Python trait inheritance in Scala is just multiple inheritance in these languages.
Nevertheless, computing the method resolution order (or class precedence list) is done differently in Scala. In CL or Python classes are ordered using two principles:
- every class after it's superclasses (super traits)
- If two classes appear in the same superclass list, their order is this
list has to be preserved in the final class precedence list.
If these two relations induce a partial order, a linearization of this order is used as class precedence list. Scala seems to pursue a different approach, In a recursive approach it concatenates the previously constructed linearizations of all super classes in order, and just does not add any class again, which are already present. While this approach preserves property 1 from above, property 2 may be violated, for example:
class Base
trait A extends Base
trait B extends A
class Test extends Base with B with A
This results in the order Test, B, A, Base. Note that B actually is after A, although A is written after B in the superclass list of Test. Let's compare this to the same example in Python (Note that in Python the first superclass is the most important not the last one as in Scala):
class Base:pass
class A(Base):pass
class B(A):pass
class Test(A,B):pass
Entering this in the Python interpreter yields an exception:
Cannot create a consistent method resolution order (MRO) for bases A, B. This is expected since A has to come before B as A is a superclass of B, but B has to come before A as A is after B in the superclass list of Test. This cycle cannot be linearized.
In my opinion the Python result is more natural and error safe as the superclass order always respects the way I specify it, if this is not possible an error occurs. In Scala, on the other hand, if a supertrait does something weird, my superclass order can get mixed and become unexpected. If I rely on the order some stacked behaviour, my code can become wrong and the error is
very subtle and hard to track. (Imagine I add behaviour as a trait another trait already implements, even worse imagine this changes in some library version).
Nevertheless, if my supeclasses can be linearized in the Python sense, the obtained order seems to match the order Scala obtains for the same hierarchy, but I am not quite sure about this, I would have to prove this on paper.
So my question is: Why is Scala more relaxed than Python or Common Lisp constructing the class precedence list? Is there a good reason or is it just the way someone implemented it once (maybe because he was unaware of the issue)? Will this algorithm change to the Python version in the future (2.12/13/...)?
Thanks for reading my post,
Thomas