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