Hey All,

So, given the abundance of positive responses ;) for my "class methods don't inherit" proposal, I have decided to withdraw that proposal (see my last response on the thread). Of course, this means we now have to work out the details of exactly *how* they get inherited in all situations. The trickiest one being in the presence of custom metaclasses. So, onto the questions.

(NOTE: if you get bored about half-way through this mail (and you probably will), there is a conculsion/wrap-up at the very bottom that you can probably safely skip ahead too)

Should custom metaclasses are "inherited" along "normal" subclass lines?

This would mean that if Foo uses CustomMeta as it's metaclass, any subclass of Foo will do the same. This is something Larry mentioned, and something I had been thinking about a lot myself and discussed recently with Rob Kinyon. I drew a diagram in my response to Larry that looked like this:

  Class
    ^
    :
CustomMeta
    ^
    :
  eFoo<.......eBar
    ^          ^
    |          |
   Foo<.......Bar

This shows the structure which would be created. The result is that method dispatch would go like this:

Bar.foo
  eBar.get_method(foo)
    eFoo.get_method(foo)
      CustomMeta.get_method(foo)
        Class.get_method(foo)
          ! No Method Found Error !

I think this makes sense in many ways since CustomMeta can theoretically add capabilities to the Foo class, which one would want inherited by subclasses of Foo. (NOTE: if CustomMeta adds instance methods to Foo, they will get inherited, mostly I am talking about class functionality here)

However, I can also see where it would make sense for this *not* to behave this way. But if we the opposite approach, the eigenclass/ metaclass hierarchy begins to get more complex.

To start with, we would need to create another anon-class (called xFoo here) which uses multiple inheritence to inherit from eFoo and CustomMeta, then eBar would inherit from eFoo (and through eFoo, to Class), this would keep CustomMeta out of Bar's method dispatch path.

  Class
    ^^.......
    :       :
CustomMeta  :
    ^       :
    :       :
  xFoo...>eFoo<...eBar
    ^               ^
    |               |
   Foo<............Bar

The resulting method dispatch paths would be:

Bar.foo
  eBar.has_method(foo)
    eFoo.has_method(foo)
      Class.has_method(foo)
        ! Method Not Found Error !

Note the lack of CustomMeta in the dispatch path, where as in the above example it was there.

This on it's own is not too bad, however, when we add another metaclass, it starts to get busier. Our anon-class xBar must inherit from eBar and CustomMeta2 (just like xFoo did). Then eBar must be connected to eFoo in order to inherit from it, but not pic up any of CustomMeta's methods.

  Class<...............
    ^^.......         :
    :       :         :
CustomMeta  :    CustomMeta2
    ^       :         ^
    :       :         :
  xFoo...>eFoo       xBar....>eBar
    ^       ^         ^        :
    |       :.........|........:
    |                 |
   Foo<..............Bar

The method dispatch path for this is pretty much the same as above, with the addition of CustomMeta2. This example now actually brings up another issue.

What should the superclass ordering be within the x* classes?

If eBar comes first, followed by CustomMeta2, then we get the following method dispatch path:

Bar.foo
  xBar.has_method(foo) # xBar should never have any of it's own method
    eBar.has_method(foo)
      eFoo.has_method(foo)
        CustomMeta2.has_method(foo) # this would fall here under C3
          Class.has_method(foo)
            ! Method Not Found Error !

But if CustomMeta2 comes first, followed by eBar, then we get the following method dispatch path:

Bar.foo
  xBar.has_method(foo)
    CustomMeta2.has_method(foo)
      eBar.has_method(foo)
        eFoo.has_method(foo)
            Class.has_method(foo)
              ! Method Not Found Error !

The question really is, which has precedence, the custom metaclass, or the local class methods (including class methods inherited along "normal" class lines)?

Now, all these method dispatch paths are using the C3 MRO. And personally I find neither of these approaches to be the best. One alternate (but kind of whacky) approach would be to not use C3 here, but instead use breath-first traversal.

Now, this may seem really odd at first, but given the highly regular structure of these class groupings, it might make the most sense. Here is what that method dispatch path might look like:

Bar.foo
  xBar.has_method(foo)
    eBar.has_method(foo)
      CustomMeta2.has_method(foo)
        eFoo.has_method(foo)
          Class.has_method(foo)
            ! Method Not Found Error !

Notice how the eBar (which hold's Bar's class methods) is first, followed by the CustomMeta2 class, the followed by eFoo, then onto Class.

This specialized dispatching behavior could (I think) be isolated in the x* classes MRO/dispatcher, which if done properly would be a virtually transparent change in the meta-model. And keep in mind that the x* classes would clearly be a meta-level/non-user-accessible thing.

But before we go to far along these lines, lets examine this example using the "metaclasses are inherited" approach. It would look like this:

  Class<..........
    ^            :
    :            :
CustomMeta  CustomMeta2
    ^            ^
    :            :
  eFoo<.........eBar
    ^            ^
    |            |
   Foo<.........Bar

Now, this is now where this approach gets a little quirky. We need to decide the order of the eFoo and CustomMeta2 in the superclass list, because they have 2 very different MROs with C3.

If eFoo comes before CustomMeta2, the we have the following MRO:

  eBar, eFoo, CustomMeta, CustomMeta2, Class

This one is odd since CustomMeta will override CustomMeta2, which is almost certainly not what we want. If CustomMeta2 comes before eFoo, we have the following MRO:

  eBar, CustomMeta2, eFoo, CustomMeta, Class

My guess is that this one is more "correct" since it gives the custom metaclass precedence over the inherited methods. So if we go with the second one (which I would recommend), we get the following dispatch path.

Bar.foo
  eBar.has_method(foo)
    CustomMeta2.has_method(foo)
      eFoo.has_method(foo)
        CustomMeta.has_method(foo)
          Class.has_method(foo)
            ! Method Not Found Error !

Lets take it one more step again. Here we add Baz into the mix, Baz does not use a custom-metaclass, so it only needs to inherit from eBar. Then we introduce Blah, which uses CustomMeta3. We now have to create the same 3 class set up again (xBlah, eBlah and CustomMeta3).

  Class<...............<............................
    ^^.......         :                            :
    :       :         :                            :
CustomMeta  :    CustomMeta2                   CustomMeta3
    ^       :         ^                            ^
    :       :         :                            :
  xFoo...>eFoo       xBar....>eBar<... eBaz      xBlah...>eBlah
    ^       ^         ^        :        ^^         ^        :
    |       :.........|........:        |:.........|........:
    |                 |                 |          |
   Foo<..............Bar<..............Baz<.......Blah

The method dispatch for these classes if shown below. Again, I will show a few different approaches to the method dispatching.

First using regular C3, with, for example, eBar coming before CustomMeta2 in the superclass list.

Foo   =>   xFoo, eFoo, CustomMeta, Class
Bar   =>   xBar, eBar, eFoo, CustomMeta2, Class
Baz   =>   eBaz, eBar, eFoo, Class
Blah  =>   xBlah, eBlah, eBaz, eBar, eFoo, CustomMeta3, Class

Next, also using C3, but we reverse the superclass list (ex: CustomMeta2 comes before eBar).

Foo   =>   xFoo, CustomMeta, eFoo, Class
Bar   =>   xBar, CustomMeta2, eBar, eFoo, Class
Baz   =>   eBaz, eBar, eFoo, Class
Blah  =>   xBlah, CustomMeta3, eBlah, eBaz, eBar, eFoo, Class

To be honest, at this depth I am not sure which approach I prefer. Part of me thinks keeping the custom metaclasses earlier in the dispatch path is a good thing. Let's examine the third dispatch order proposed above of making all x* classes use breadth-first dispatching (and placing the e* classes first in the superclass list). This would result in the following MROs:

Foo   =>   xFoo, eFoo, CustomMeta, Class
Bar   =>   xBar, eBar, CustomMeta2,  eFoo, Class
Baz   =>   eBaz, eBar, eFoo, Class
Blah  =>   xBlah, eBlah, CustomMeta3, eBaz, eBar, eFoo, Class

To be honest, the more I look at this, the more sane I think it is. Using the breadth first traversal our local class methods (those in e* classes) come first, followed by anything from the metaclasses, followed after that by all inherited class methods.

Lastly, we have the same class hierarchy, but using the "metaclasses are inherited" scheme.

  Class<..........<...........................
    ^            :                           :
    :            :                           :
CustomMeta  CustomMeta2                 CustomMeta3
    ^            ^                           ^
    :            :                           :
  eFoo<.........eBar<.........eBaz<.........eBlah
    ^            ^             ^             ^
    |            |             |             |
   Foo<.........Bar<..........Baz<..........Blah

Here are the resulting MROs with the e* classes first in the superclass list.

Foo   =>  eFoo, CustomMeta, Class
Bar   =>  eBar, eFoo, CustomMeta, CustomMeta2, Class
Baz   =>  eBaz, eBar, eFoo, CustomMeta, CustomMeta2, Class
Blah => eBlah, eBaz, eBar, eFoo, CustomMeta, CustomMeta2, CustomMeta3, Class

You will notice that the CustomMeta classes tend to clump around the end of the list, and run in reverse order. I think this is probably flat out wrong. If we try reversing the superclass list, so that the custom metaclasses come before the inherited e* classes, we get these MROs.

Foo   =>  eFoo, CustomMeta, Class
Bar   =>  eBar, CustomMeta2, eFoo, CustomMeta, Class
Baz   =>  eBaz, eBar, CustomMeta2, eFoo, CustomMeta, Class,
Blah => eBlah, CustomMeta3, eBaz, eBar, CustomMeta2, eFoo, CustomMeta, Class,

There is a certain sanity to this last set of MROs. The e* classes always come before the custom metaclasses going all the way down the list. It even correctly folds in the eBaz (the one without the metaclass).

== CONCLUSION / WRAP-UP

So, now that I have sufficiently bored you all to tears, I will do a quick re-cap of the main question, and the possible solutions.

Should metaclasses be "inherited" along normal class lines?

Meaning that if Foo uses a custom metaclass, and Bar isa Foo, then Bar also uses that metaclass.

If we say yes, then I think it is clear that in order to get a sane method and predictable resolution order the custom metaclass would always need to precede the inherited eigenclass in the superclass list. (see the last example above).

If we say no, then we need to introduce another anyonomous class into our mix, which we call an 'x' class. The 'x' class would need to use a breath-first dispatch order in order to produce a sane and predictable method resolution order.

So, what say you O @Larry of the List how will our meta-classes behave?

Thanks :)

Stevan


Reply via email to