The Artima Developer Community
Sponsored Link

Weblogs Forum
Setting Multiple Inheritance Straight

13 replies on 1 page. Most recent reply: Mar 24, 2010 10:19 PM by Ethan Furman

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 13 replies on 1 page
Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Setting Multiple Inheritance Straight (View in Weblogs)
Posted: Jan 10, 2009 3:27 AM
Reply to this message Reply
Summary
I have argued many times that multiple inheritance is bad. Is it possible to set it straight without loosing too much espressive power? My strait module is a proof of concept that it is indeed possible. Read and wonder ...
Advertisement

As you may know, for some time I have been running a campaign against multiple inheritance and mixins - you may want to read the conclusion of the third article of the series Things to Know About Super and the first and the second article of the series Mixins considered harmful. Following that route, last week I decided to release a module I wrote this summer, the strait module, which implements traits for Python.

The implementation is inspired by the 2003 paper Traits - Composable Units of Behavior. Traits are simple since they cannot have common methods and the method resolution order is trivial. There is an implementation of the concept in the Smalltalk implementation Squeak.

The strait module was written as a design exercise, to prove a few points:

  • that you can replace multiple inheritance with a less powerful but also simpler and less dangerous mechanism, without losing much expressive power;
  • that a language such as Python is powerful enough that you can implement traits in 100 lines by using single inheritance only;
  • that you can keep a kind of method cooperation even using traits, but in a simpler way than using multiple inheritance, basically by setting straight the original hierarchy.

The documentation of the strait module is intended for language designers, framework writers and advanced Python programmers. It actually was written for the guys of the python-dev list, as a companion to a thread about my articles on super. It is not intended for the average Joe programmer, and it is somewhat technical, focusing on the details on the Python implementation. On the other hand, knowing that alternatives to multiple inheritance and mixins exist in my opinion is good for everybody.

Thus, I have decided to supplement the documentation of the strait module with a few notes explaining what traits are, the differences with multiple inheritance and mixins and what we mean by method cooperation. The notes here are intended for any programmer with experience in OOP, and they are not Python specific at all.

Multiple inheritance and method cooperation

Multiple inheritance, mixins and traits are usually considered advanced techniques of object oriented programming, since the most popular languages (Java, C#, VisualBasic, PHP) do not support them, or support them in a poor way (C++). On the other hand, those techniques are pretty common in the coolest languages out there, such as Python (featuring multiple inheritance), Ruby (featuring mixins) and Scala (featuring "traits"). I am quoting the term "traits" when referred to Scala, since Scala traits are more similar to Python mixins than to Squeak traits. Actually, Scala traits can be composed when they override the same method and the order of the composition determines the resulting pattern of super calls: that means that Scala traits have basically all the complications of Python mixins, which I would rather avoid.

Multiple inheritance is the most general technique among the three cited before: mixins can be seen as a restricted form of multiple inheritance and traits as a restricted form of mixins. Multiple inheritance is available in various languages, such as C++, Common Lisp, Python, Eiffel, and others. In a multiple inheritance language, a class can have more than one parent and thus can inherit methods and attributes from more sources at the same time. Maintaining code taking advantages of (multiple) inheritance is nontrivial, since in order to understand how a class works, one needs to study all of its parents (and the parents of the parents, recursively).

That means that there is a strong coupling of the code: changing any method in any ancestors has an effect on the class. To some extent this is is inevitable, since the other face of code reuse if code coupling (you cannot have one without the other) and one has to cope with that. Also, you have the same problem even with single inheritance, when you have a deep hierarchy. However, multiple inheritances adds another level of complication.

For instance, the order of the parents is significant: a class C1 inheriting from P1 and P2 does not necessarily behave the same as a class C2 inheriting from P2 and P1 where the order of the parents is inverted. The reason is that for common methods, i.e. methods with the same name, the methods of P1 have the precedence over the methods of P2 for the class C1(P1, P2), but not for the class C2(P2, P1). Since the common methods are silently overridden and programmers are not quite good at remembering the ordering, that may give raise to subtle bugs.

The situation is worse if one looks at the higher order ancestors: the order of overriding (the so called MRO, Method Resolution Order) is definitely non trivial: I actually wrote a long essay on the subject, describing the Python MRO and I refer to that reference for the details. While that reference is Python specific, the concept of method resolution (also called linearization in the Lisp world) is general and applies to many languages, including Dylan and Common Lisp.

If you want to know more about the linearizations of Dylan and Common Lisp, you should look at this paper. On the other hand, if you are a reader of my Scheme series or a Scheme practitioner, I suggest you to read the paper Scheme with Classes, Mixins, and Traits, which describes the object system of PLT Scheme, which support both cooperative mixins and traits in the Squeak sense.

Scala does not support full multiple inheritance, but its traits are nearly as powerful (the only difference between a trait and a regular class is that the trait does not define a constructor) and nearly as complicated, therefore I would consider Scala in the same class with Python and Common Lisp. If you want to look at how Scala works, you may look the Scala Overview paper. Theoretically, the Python MRO is the best one, since it is monotonic, but in practice all MROs are quite complicated.

The point to notice is that the complication of the MRO is by design: languages with a non-trivial MRO where designed this way to make possible method cooperation via super calls. That means that if both parents P1 and P2 define a method m, a child class C can override it and still have access to the m methods of the parents via super: C.m will call first P1.m and then P2.m, if P1.m features a super call itself.

Of course, this is just one possible design: different languages may adopt different designs. For instance the Eiffel language implements multiple inheritance, but it raises an exception when two methods with the same name are present: the programmer is forced to specify an explicit renaming (this is basically what happens for traits).

Years ago, I thought such a design to be simplistic (even stupid) and very much inferior to the Python cooperative design: nowadays I have had more experience with real life large object oriented systems using multiple inheritance and I have come to appreciate "stupid" designs. Actually, nowadays I think Smalltalk made the right choice thirty years ago, deciding to not support multiple inheritance nor mixins.

Mixins and traits without multiple inheritance

In practice, the overriding problem is not very frequent (it is serious when it happens, but it rarely happens) since usually frameworks are designed to mix independent sets of functionality. Usually one does not need the full power of multiple inheritance: mixins or traits are powerful enough to implement most frameworks.

In a language with multiple inheritance it is natural to implement mixins as classes. However, this is not the only solution. In general, we can speak of mixin programming in any language where it is possible to inject methods in the namespace of a class, both statically before class creation or dynamically after class creation.

For instance, Ruby does not support multiple inheritance, bit it does support mixins since it is possible to include methods coming from a module:

class C_with_mixin < C:
   include M # M is a module

There is an advantage in this approach: modules have no parents and there is no concept of method resolution order, so it is much easier to figure out what a mixin does, as compared to figure out what a mixin implemented as a class in a multiple inheritance hierarchy does. On the other hand, there is no method cooperation in the sense of Python or Scala super or CLOS call-next-method. There is a limited cooperation between parent and sons only, since Ruby super (like Java super), is able to dispatch to the parent class only. This is not necessarily a bad thing, though.

Ruby mixins are much simpler than Scala traits or Python mixins, but they still suffer for the ordering problem: mixing the module M1 and the module M2 is different than mixing the module M2 and the module M1: if the modules contain methods with the same name, changing the composition order affects the resulting class.

Traits were invented just to solve this problem: common methods raise an error unless the programmer specifies the precedence explicitly, or she renames the methods. After that, traits commute. Traits are therefore the most explicit and safest technique, whereas multiple inheritance is the most fragile technique, with mixins in between.

A proper implementation of traits should also include introspection tools such that a class can be seen both as a flat collection of methods and as a composite entity (the original paper about traits explain this point pretty well). That should help with the namespace pollution problem by giving to the developer the ability to see the class as a composition of traits (one could argue that in Python pydoc allows you to see the origin of the methods as coming from parent classes, but that support is insufficient to manage situations with complicate inheritance hierarchies and lots of methods).

In Python you can also implement mixins without inheritance simply by dynamically adding methods to a class, starting from a method dictionary M:

class C_with_mixin(C):
   pass

for name in M: # M is a dictionary of methods
   setattr(C_with_mixin, name, M[name])

Implementing Ruby mixins in Python is therefore trivial, you can just read the methods from a module dictionary. Implementing traits is a bit less trivial, since you must check for common names and raise an error in that case. Moreover you must be careful with ordering issues: the traits paper says that methods coming from a trait must take the precedence over methods coming from the base class, but they must not take the precedence over methods defined in the class.

I took some liberty with my own implementation of traits, which was just inspired by the Squeak implementation, but it is not the same. In particular, I added some support for cooperation of traits, i.e. there is a kind of super, but its functionality is limited with respect to the regular super, and it is a bit more akward to use: this is on purpose, to discourage designs based on method cooperation, which I think are fragile and not to be recommended (again, see the third article on my series about super). Still, in the very few special case where one wants cooperation, that is possible indeed.

All those points are explained in the documentation of the strait module, so you should look there, if you are really interested in the subject. Here I will just add that I still prefer generic functions to traits. Nevertheless, traits may have a role if you want to follow the traditional route of having methods inside classes, and they are a smaller leap from traditional object oriented programming. Moreover, it is much easier to convert a pre-existing framework from using mixins to using traits than to convert it generic functions.

If you want to play with traits in Python, you are welcome to try the strait module. Enjoy!

Post scriptum. I realize that this post may be misinterpreted, so let me make clear a couple of points:

  1. I am not asking for removing multiple inheritance in Python and replacing it with traits. However, I am saying to people writing new languages: think twice before adding multiple inheritance. Certainly it is more difficult to implement than traits; moreover, I am arguing that it makes life more difficult for yours users too.
  2. I do not think traits are the best thing after sliced bread. They are a bit better than multiple inheritance, but I still recommend to keep things simple and to use both (single) inheritance and traits as little as possible.


Matthew Wilson

Posts: 4
Nickname: mw44118
Registered: Sep, 2008

Re: Setting Multiple Inheritance Straight Posted: Jan 10, 2009 3:02 PM
Reply to this message Reply
Can you explain the difference between traits and the prototype system in languages like javascript and lua (and self)?

Mark Roddy

Posts: 1
Nickname: markroddy
Registered: Jan, 2009

Re: Setting Multiple Inheritance Straight Posted: Jan 10, 2009 4:06 PM
Reply to this message Reply
Michele,
I've been following your posts via the Artima RSS feed. When I first read your posts on mixins being harmful I was intially dismissive. I generally don't like arguments against language features based on the fact that you *could* shoot yourself in the foot. Being someone who formerly developed in Java and currently does so in Python, I know the pain of getting to a leaf case in programming, aka the exception to the rule, where it does make sense to use features/approaches that can be otherwise abused and not being able to do so because the technology I am using prevents me from doing so. Mixins, like GOTO, are easily and often abused, but with experience comes the ability to recognize the cases when using these techniques makes sense. I don't like the idea of forcing people with such abilities to work harder in order to accomidate those that don't already have such experience. I'd argue that its the responsibility of those on a development team to mentor those with less experience to learn the exceptable uses of such techniques.

That being said, I'm growing more interested in your ideas after reading this post. It almost seems like you're starting to develop a new programming paradigm based on OOP. I find this much more intriguing and constructive then typical complaints about OOP on the internet that come across more as a rant along the lines of "A guy I worked with abused this language feature which made my life difficult so no one should ever use it". I myself have been guilty of making such generalizations in the past, and admittedly, I dismissed your first post about mixins as being along the same line, though the 'Considered Harmful' title did make it easy to be dismissive. I am now starting to come around, and I'm looking forward to hearing more of your ideas. I've always wished that there was a language/programming paradigm whose very nature discouraged feature abuse like you've noted about mixins but still made them easy to use in the cases that they are warranted. I don't know that this is a goal of yours or if its even possible, but I can always hope.

-Mark

Trond Olsen

Posts: 14
Nickname: tolsen
Registered: Mar, 2007

Re: Setting Multiple Inheritance Straight Posted: Jan 10, 2009 9:07 PM
Reply to this message Reply
> Multiple inheritance is the most general technique among the three cited before: mixins can be seen as a restricted form of multiple inheritance and traits as a restricted form of mixins.

Why not just call them that?

John Cowan

Posts: 36
Nickname: johnwcowan
Registered: Jul, 2006

Re: Setting Multiple Inheritance Straight Posted: Jan 10, 2009 9:42 PM
Reply to this message Reply
I've been developing a model that's closely related to your traits model, but with the following differences:

1) The methods directly defined in the class constitute a trait, so if there are name conflicts between methods incorporated from a trait and methods in the class, it's an error, just as in the case of methods incorporated from two different traits. Furthermore, all classes smell the same: any class can be incorporated as a trait into any other class, provided there are no loops in the graph.

2) Instance variables, unlike methods, are trait-local, and a compiler should warn if a method does not access at least one instance variable (which conceptually means that it belongs in a different trait, perhaps one with no instance variables).

3) There is no such concept as a base class, although there is subtyping in the style of Java interfaces: a class may declare that it is a subtype of one or more classes, which means that its public methods are a superset of the public methods of the supertype classes (but do not in any way share implementations with them unless they are explicitly incorporated as traits).

4) There is nothing like super; if you want to call a method in a trait that has the same name as you do, rename it and call it explicitly.

I have not yet decided if incorporating a trait is transitive or not: what's your view?

Daniel Berger

Posts: 1383
Nickname: djberg96
Registered: Sep, 2004

Re: Setting Multiple Inheritance Straight Posted: Jan 10, 2009 10:28 PM
Reply to this message Reply
I saw this coming a mile away. "Mixins blew up on me once, so let's overcorrect with Traits!". I've seen it before, and I'm sure we'll see it again.

Anyway, the problem with Traits is that all you're doing is replacing static typing with static composition. In a static language like Scala that's fine. But in a dynamic language, it's completely out of place.

Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: Setting Multiple Inheritance Straight Posted: Jan 11, 2009 12:07 AM
Reply to this message Reply
> It almost seems like you're
> starting to develop a new programming paradigm based on
> OOP.

No, no, I am not doing anything original or particularly deep, I am just popularizing ideas which have been around for a while. Traits are not a new paradigm, they are a kind of multiple inheritance for the poor man, but I am arguing that they are enough.

> I've always wished that there was a language/programming
> paradigm whose very nature discouraged feature abuse like
> you've noted about mixins but still made them easy to use
> in the cases that they are warranted. I don't know that
> this is a goal of yours or if its even possible, but I can
> always hope.

Me too. The strait module is marked as experimental. It is there for people which are working on a framework based on mixins and wanting to try traits instead. Will traits be enough for them? Will traits make a difference for users of
that framework? I hope so, but only real life code can give us the answer. So, please try it and give me feedback!

Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: Setting Multiple Inheritance Straight Posted: Jan 11, 2009 12:10 AM
Reply to this message Reply
> Anyway, the problem with Traits is that all you're doing
> is replacing static typing with static composition. In a
> static language like Scala that's fine. But in a dynamic
> language, it's completely out of place.

Honestly, I haven't a clue about what you are talking about.
Care to be more explicit?

Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: Setting Multiple Inheritance Straight Posted: Jan 11, 2009 2:08 AM
Reply to this message Reply
> I have not yet decided if incorporating a trait is
> transitive or not: what's your view?
I am not sure of what you mean by transitivity: perhaps that if a class B incorporates a trait T and C is a subclass of B, then C incorporates T as well? That seems sensible behavior to me and my implementation works this way. My approach is not symmetric though: I can compose a base class with one or more traits, but what happens when I try to compose two traits without a base class is undefined (I may decide to raise an error, I haven't decided yet). So I do not worry about questions like "if trait T1 incomporates trait T2 and T2 incomporated T3, does T1 incomporates T3 as well?" Anyway, in a symmetric system like the one you are suggesting I would expect an affermative answer.

John Cowan

Posts: 36
Nickname: johnwcowan
Registered: Jul, 2006

Re: Setting Multiple Inheritance Straight Posted: Jan 13, 2009 7:19 PM
Reply to this message Reply
> I am not sure of what you mean by transitivity: perhaps
> that if a class B incorporates a trait T and C is a
> subclass of B, then C incorporates T as well? That seems
> sensible behavior to me and my implementation works this
> way.

That seems reasonable for you. For me, there is no base class - superclass relationship with inheritance: incorporating a trait is the only kind of inheritance there is, and superclasses are just supertypes.

> My approach is not symmetric though: I can compose a
> base class with one or more traits, but what happens when
> I try to compose two traits without a base class is
> undefined (I may decide to raise an error, I haven't
> decided yet). So I do not worry about questions like "if
> trait T1 incorporates trait T2 and T2 incomporated T3,
> does T1 incorporates T3 as well?"

Fair enough.

> Anyway, in a symmetric
> system like the one you are suggesting I would expect an
> affermative answer.

The objection is that just because you bring in the methods defined in T1 *considered as a trait*, you don't necessarily want the methods that T1 *considered as a class* incorporates into itself. So I think I'll say that incorporating T1 brings in just the methods defined in T1, and that if you want the methods of T2, you know where to find them.

Raoul Duke

Posts: 127
Nickname: raoulduke
Registered: Apr, 2006

Re: Setting Multiple Inheritance Straight Posted: Feb 12, 2009 1:57 PM
Reply to this message Reply
> Honestly, I haven't a clue about what you are talking
> about.

me 3, and i would love to see an answer so i can learn more. (or find out it was trolling junk spam. :-)

Ethan Furman

Posts: 3
Nickname: stoneleaf
Registered: Sep, 2009

Re: Setting Multiple Inheritance Straight Posted: Mar 23, 2010 3:02 PM
Reply to this message Reply
The strait module looks very interesting, and I am intrigued. But I am also puzzled. In reading the documentation for the straight module, I am having trouble seeing how it will help with name-space pollution. Taking the plone example you gave, if that system were redone using traits, would not the end result still be an object with 600+ attributes, methods, etc.?

Michele Simionato

Posts: 222
Nickname: micheles
Registered: Jun, 2008

Re: Setting Multiple Inheritance Straight Posted: Mar 24, 2010 9:40 AM
Reply to this message Reply
> if that system were redone
> using traits, would not the end result still be an object
> with 600+ attributes, methods, etc.?

Yes, traits are a compromise for people wanting something similar to multiple inheritance (even though simpler and more controlled). They do not solve the namespace pollution problem. To solve that, you need an approach based on composition (see my posts about mixins).

Ethan Furman

Posts: 3
Nickname: stoneleaf
Registered: Sep, 2009

Re: Setting Multiple Inheritance Straight Posted: Mar 24, 2010 10:19 PM
Reply to this message Reply
> Yes, traits are a compromise for people wanting something
> similar to multiple inheritance (even though simpler and
> more controlled). They do not solve the namespace
> pollution problem. To solve that, you need an approach
> based on composition (see my posts about mixins).

From what I've studied so far, traits are better than a compromise. ;)

Thanks for the tip about using import inside the class structure -- I had not realized I could do that. I love Python!

Flat View: This topic has 13 replies on 1 page
Topic: Software Development Has Stalled Previous Topic   Next Topic Topic: (Re-)Introducing the Heron Programming Language

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use