On 7/19/05, Damian Conway <[EMAIL PROTECTED]> wrote: > > And now maybe you see why I am so disgusted by this metric. You see, > > I'm thinking of a class simply as the set of all of its possible > > instances. > > There's your problem. Classes are not isomorphic to sets of instances and > derived classes are not isomorphic to subsets.
Ahh, I understand now. If you think that way, then there is no way to convince you, since that is the piece of mathematics that my whole argument is based on. Please seriously consider this world model and its implications (especially regarding my new thread about superclassing). I'll give up on the theoretical side now. ~~~~~ I've just released Class::Multimethods::Pure for an account of how pure ordering works in practice. As the first case study, see Class::Multimethods::Pure (it bootstraps itself :-). The ambiguities that it pointed to me turned out to be very important design-wise, and I noticed that under manhattan distance it would have silently worked and then broken (in ambiguity) later. Readers, do your best to follow along. This is pretty complex, but that's exactly what I'm arguing: that a derivation metric like Manhattan will decieve you when things get complex. The piece was the junction factoring that I described in my other thread (I use junctions in MMD to implement type junctions). At first I had this model: Object |- Junction |- Disjunction |- Conjunction |- Injunction |- Constrained |- Subtype |- PackageType |- ... And the multis defined as: multi subset (Junction, Object) {...} multi subset (Object, Junction) {...} multi subset (Junction, Junciton) {...} Which made recursive calls to subset on their constituent types. The various Junction subclasses have a "logic" method which knows how to evaluate the junction in boolean context. I also had: multi subset (Subtype, Object) {...} multi subset (Object, Subtype) {...} multi subset (Subtype, Subtype) {...} Then: multi subset (Package, Package) {...} Etc. for all the other non-combinatoric types, and: multi subset (Object, Object) { 0 } As the fallback. Naturally, when I called: subset(Disjunction.new(...), Subtype.new(...)) I got an ambiguity. Did you mean (Junction, Object) or (Object, Subtype)? Something was wrong with my design: I needed to structure my types to tell the MMD system which one I wanted to thread first. This is an error that you'd expect, right? I didn't tell the compiler something it needed to know. However, look at the applicable candidates: subset(Junction, Object) # 1 + 2 = 3 subset(Object, Subset) # 2 + 0 = 2 subset(Object, Object) # 2 + 2 = 4 The second variant, (Object, Subset) matches. Oh goody, it worked! Now I can go on my merry way documenting and releasing my module. Now Mr. Joe Schmoe comes along and decides that he wants to write a new subtype type -- one that accepts his new statically-analyzable subtyping language or something. He decides to reuse code and derive from the existing Subtype type. The new type hierarchy follows: Object |- Junction |- Disjunction |- Conjunction |- Injunction |- Constrained |- Subtype |- MagicSubtype # the new type |- PackageType |- ... Now look at what happens for subtype(Disjunction.new(...), MagicSubtype.new(...)): subset(Junction, Object) # 1 + 2 = 3 subset(Object, Subtype) # 2 + 1 = 3 subset(Object, Object) # 2 + 2 = 4 Oh no! An ambiguity! What the hell, Joe's just trying to extend Subtype a little, and now he has to write a specialized MMD variant just for that, which delegates *exactly* to the (Object, Subtype) variant. I'll also point out that if you remove the Constrained intermediate type, which I did (!), you also end up in ambiguity for the call subtype(Disjunction.new(...), Subtype.new(...)). And that's it. Two innocent changes, and a working program breaks into ambiguity errors. And the person who sees the ambiguity errors is not the person who wrote -- or even touched -- the multimethods. Keep in mind: these multimethods could be for internal use, so the extender may not even know they exist. Using pure ordering, we saw the ambiguity early and were forced to think about the design and come up with one that passed the tests. When I did that, I was able to factor things to avoid duplication and needless disambiguating variants[1]. It is impossible to break the new factoring by simply deriving from any class. You would have to add a new generic, like Junction, to the top in order to break existing code. Manhattan distance suffers from the same problem. See the supertyping thread for a solution :-) I'm seeing after this case study, and something that I suspected all along, that Manhattan MMD is to pure ordering as mixins are to roles. Roles don't provide any extra semantics over mixins: they add only errors. But those errors are very important to large-scale development. They help you catch things that only extenders of your module would normally find out. Luke [1] My biggest fear is that the average user won't be able to come up with such a factoring. However, I assume that there are relatively few techniques that you need to know in order to keep things safe, and these are things that you ought to be doing under a manhattan metric anyway, as I have just demonstrated. The early error gets you to ask your local mailing list instead of publishing your fragile module.