Originally posted on the mailinglist here. Mostly copied it here so I can add it to Pocket.
Here are some problems/limitations of existing OO/GF systems that I don't intend to repeat:
A) They provide only a single declaration point for all superclasses of a class B) They consider the local declaration order of superclasses to be significant C) They conflate hierarchies and graphs containing sideways edges having nothing to do with hierarchy D) They provide only a single slot for type/class per object E) They allow conflicting local superclass declaration orders, creating cycles in the graph not detected until subsequent derivation
Image:
(class Horse :supers [Animal])
Now suppose some user of Horses, but not the library author, wants to consider Horses to be Transportation. Due to (A) they can't, without convincing the author of the library to change the declaration to include Transportation in the supers:
(class Horse :supers [Animal Transportation])
Here's where (B) rears its ugly head. At this point in time there may be no significant basis for determining the order in which these declarations should occur. It is not an inherent property of the derivation relationship (C), but must be specified here and now due to the fact that the derivation declaration does 2 jobs (derivation + precedence). It might never matter, but if it does matter it will be because eventually some method is defined on both Animal and Transportation, and a decision needs to be made as to which should apply to Horse, e.g.:
(allowed-on-road Animal) -> false (allowed-on-road Transportation) -> true
in order to make (allowed-on-road Horse) work the way we want (i.e. true), and presuming we want to inherit our behavior rather than specify it (the laziness presumption), we'd prefer Transportation precede Animal in the supers declaration of Horse. Another trip to the library author.
Here's where (D) rears its ugly head - this is a global declaration that universally applies. One could easily imagine another method where it would be preferable that Animal preceded Transportation when applied to Horses (the library author might say no)
As for (E), if we later create:
(class Camel :supers [Transportation Animal])
no advances in genetics can allow us to make a HorseCamel, since the resulting graph can't be ordered. (We can't put HC on the front of a merge of H->A->T and C->T->A that preserves their relative orders)
These are just OO devices, not logical systems - they are just arbitrary mechanisms full of corner cases and creeping complexity. I don't know why we are in such a hurry to recreate them.
Here's how I think about it:
- Hierarchies have nothing to do with multimethods/GFs per se.
- They are created by independent, atomic, logical statements about derivation relationships
- Those statements can come in any order, or place
- The logical statements are validated to not create cycles (i.e. when considered as a set of logical assertions, they include a transitivity rule and one asserting a parent can't be a child of its child, plus a consistency check)
This gives us a nice logical construct with general applicability.
We can then think about leveraging hierarchy in a dispatch mechanism. Using a hierarchical system in dispatch matching means we can inherit behavior, and, within a lineage, ensures a logical precedence due to the, well, linear nature of lineages. Multiple inheritance is also not a problem, as separate multimethods can be defined on separate trees without conflict or ambiguity.
It is only when you have defined methods of the same multimethod on two independent paths to the same child that things cannot be logically resolved. This is something you should do only with great reluctance, as it represents a true logical ambiguity, e.g. you've said animals aren't allowed in the street, but transportation is, and a horse is both. I still contend you'll encounter it most frequently when trying to incorporate existing Java hierarchies. If you find you need it frequently other than in that case, you have a sloppy design IMO.
A monotonic ordering of the entire graph is not an essential property of a hierarchy, in fact, I think it is more of a crutch than a feature. Making logical ambiguities go away mechanically only encourages more ambiguity and brittleness in the dependencies on the mechanical resolution.
So, when there's an ambiguity we can always define a specific method, and usually should. Being lazy, we also have prefer-method to resolve it in favor of one inherited method, at the point we have the facts about the particular ambiguity, and differently for each method if we desire.
Here are the areas I'm looking to improve:
-
There's no easy way to talk about "the method you would get if you were dispatch value X". Note that this is not the same as call-next- method, which reintroduces global ordering requirements, but allows for easy explicit reuse of already-defined methods.
-
Currently, a preference doesn't encompass the path through the preferred value to its ancestors, but could.
-
If you have a set of common preferences, there's no easy way to create them in advance and share them among methods. There are issues here related to ensuring preference consistency and caching.
Can we please move forward in trying to implement something better than CLOS GFs? I have no interest in going backwards.
Rich