Luke wrote:
Thanks for your very detailed explanation of your views on the Pure
MMD scheme, Damian. I finally understand why you're opposed to it. I
could never really buy your previous argument: "Manhattan distance is
better".
That was never my *argument*, merely my statement-of-position. ;-)
Indeed, pure MMD will be ambiguous in more cases. If you think
narrowly, then it causes you to write many disambiguating cases which
*usually* end up being what a manhattan metric would give you anyway.
But you can get away from that terrible duplication by being more
precise about your types. You can define type classes using empty
roles[1], where you simply organize your type dag into abstractions
that make sense to your dispatcher. The upshot of all this is that it
forces you to think in a way so that the "*usually*" above becomes an
"always". And it involves defining more (very small) types that make
sense to humans, not gratuitously many MMD variants which are pretty
hard to think about.
I don't see how this is a big win. You're still be forced to explicitly
disambiguate. Either by defining extra variants (which is tedious), or by
defining extra types (which is subtle).
You just made my primary argument against manhattan distance for me.
If you change something in the middle of the class hierarchy,
manhattan distance causes multimethods on the leaves to change
semantics. You say that pure MMD causes them to break when a change
occurs. Isn't that better than changing?
No. If the dispatch behaviour changes under a Manhattan metric, then it only
ever changes to a more specific variant. Since MMD is all about choosing the
most specific variant, that's entirely appropriate, in the light of the new
information. If you change type relationships, any semantics based on type
relationships must naturally change.
On the other hand, under a pure ordering scheme, if you change type
relationships, any semantics based on type relationships immediately *break*.
That's not a graceful response to additional information. Under pure ordering,
if you make a change to the type hierarchy, you have to *keep* making changes
until the dispatch semantics stabilize again. For most developers that will
mean a bout of "evolutionary programming", where they try adding extra types
in a semi-random fashion until they seem to get the result they want. :-(
Perhaps I've made this argument before, but let me just ask a
question: if B derives from A, C derives from A, and D derives from
C, is it sensible to say that D is "more derived" from A than B is?
Now consider the following definitions:
class A { }
class B is A {
method foo () { 1 }
method bar () { 2 }
method baz () { 3 }
}
class C is A {
method foo () { 1 }
}
class D is C {
method bar () { 2 }
}
Now it looks like B is more derived than D is. But that is, of
course, impossible to tell. Basically I'm saying that you can't tell
the relative relationship of D and B when talking about A. They're
both derived by some "amount" that is impossible for a compiler to
detect. What you *can* say is that D is more derived than C.
Huh. I don't understand this at all.
In MMD you have an argument of a given type and you're trying to find the most
specifically compatible parameter. That means you only ever look upwards in a
hierarchy. If your argument is of type D, then you can unequivocally say that
C is more compatible than A (because they share more common components), and
you can also say that B is not compatible at all. The relative derivation
distances of B and D *never* matter since they can never be in competition,
when viewed from the perspective of a particular argument.
What we're really talking about here is how do we *combine* the compatibility
measures of two or more arguments to determine the best overall fit. Pure
Ordering does it in a "take my bat and go home" manner, Manhattan distance
does it by weighing all arguments equally.
In conclusion, the reason that manhattan distance scares me so, and
the reason that I'm not satisfied with "use mmd 'pure'" is that for
the builtins that heavily use MMD, we require *precision rather than
dwimmyness*. A module author who /inserts/ a type in the standard
hierarchy can change the semantics of things that aren't aware that
that type even exists. If you're going to go messing with the
standard types, you'd better be clear about your abstractions, and if
you're not, the program deserves to die, not "dwim" around it.
That *might* be an argument that builtins ought to do "pure ordering"
dispatch, but it isn't an argument in the more general case. Most people won't
be futzing around with the standard type hierarchy, except at the leaves,
where it doesn't matter. Most people will be using MMD for applications
development, on their own type hierarchies. Someone who's (for example)
building an image processing library wants to be able to add/merge/mask any
two image types using the most specific available method. Adding a new image
type (or supertype) might change which method is most specific, but won't
change the image processor's desire. Indeed, changing the dispatch to a now
more-specific method *is* the image processor's desire.
So, in counter-conclusion, Pure Ordering MMD maximizes ambiguity and thereby
imposes extra development costs to achieve (generally) the same effect as
Manhattan MMD achieves automatically. Both schemes can be made to detect
ambiguities, and under both schemes those ambiguities can be resolved by the
judicious addition of either variants or interim classes. However, by default,
when POMMD detects an ambiguity, it just gives up and makes that ambiguity
fatal, whereas MMMD always automatically resolves ambiguities in a plausible
manner. I know which approach I'd prefer.
Oh, and the mmd style should probably look like:
multi foo (...) is mmd<pure> {...}
multi bar (...) is mmd<manhattan> {...}
Rather than a pragma.
The pragma would merely set the default, from which traits could cause
particular multis to vary.
[1] And I find this to be useful even when using manhattan distance.
I'd like to be able to define such type classes out-of-band, like:
role Foo
defines Bar # Bar does Foo now
defines Baz # Baz does Foo now
{ }
You *really* think action-at-a-distance reverse composition is a good idea?
Seems like a maintenance nightmare to me.
Damian