The False Cognate problem and what Roles are still missing
Aristotle Pagaltzis 21 August 2008 01:40:38
Hi $Larry et al,
I brought this up as a question at YAPC::EU and found to my surprise that no one seems to have thought of it yet. This is the mail I said I’d write. (And apologies, Larry. )
Consider the classic example of roles named Dog and Tree which both have a `bark` method. Then there is a class that for some inexplicable reason, assumes both roles. Maybe it is called Mutant. This is standard fare so far: the class resolves the conflict by renaming Dog’s `bark` to `yap` and all is well.
But now consider that Dog has a method `on_sound_heard` that calls `bark`.
You clearly don’t want that to suddenly call Tree’s `bark`.
Unless, of course, you actually do.
It therefore seems necessary to me to specify dispatch such that method calls in the Dog role invoke the original Dog role methods where such methods exist. There also needs to be a way for a class that assumes a role to explicitly declare that it wants to override that decision. Thus, by default, when you say that Mutant does both Dog and Tree, Dog’s methods do not silently mutate their semantics. You can cause them to do so, but you should have to ask for that.
I am, as I mentioned initially, surprised that no one seems to have considered this issue, because I always thought this is what avoiding the False Cognate problem of mixins, as chromatic likes to call it, ultimately implies at the deepest level: that roles provide scope for their innards that preserves their identity and integrity (unless, of course, you explicitly stick your hands in), kind of like the safety that hygienic macros provide.
On Wed, Aug 20, 2008 at 3:16 PM, Aristotle Pagaltzis <pagaltzis@gmx.de> wrote:
Hi $Larry et al,
I brought this up as a question at YAPC::EU and found to my
surprise that no one seems to have thought of it yet. This is
the mail I said I'd write. (And apologies, Larry. )
Consider the classic example of roles named Dog and Tree which
both have a `bark` method. Then there is a class that for some
inexplicable reason, assumes both roles. Maybe it is called
Mutant. This is standard fare so far: the class resolves the
conflict by renaming Dog's `bark` to `yap` and all is well.
But now consider that Dog has a method `on_sound_heard` that
calls `bark`.
You clearly don't want that to suddenly call Tree's `bark`.
Unless, of course, you actually do.
It therefore seems necessary to me to specify dispatch such that
method calls in the Dog role invoke the original Dog role methods
where such methods exist. There also needs to be a way for a
class that assumes a role to explicitly declare that it wants
to override that decision. Thus, by default, when you say that
Mutant does both Dog and Tree, Dog's methods do not silently
mutate their semantics. You can cause them to do so, but you
should have to ask for that.
I am, as I mentioned initially, surprised that no one seems to
have considered this issue, because I always thought this is what
avoiding the False Cognate problem of mixins, as chromatic likes
to call it, ultimately implies at the deepest level: that roles
provide scope for their innards that preserves their identity and
integrity (unless, of course, you explicitly stick your hands in),
kind of like the safety that hygienic macros provide.
My thoughts:
Much of the difficulty comes from the fact that Mutant doesn't rename Dog::bark; it overrides it. That is, a conflict exists between Dog::bark and Tree::bark, so a class or role that composes both effectively gets one that automatically fails. You then create an explicit Mutant::bark method that overrides the conflicted one; in its body, you call the Tree::bark method (or the Dog::bark method, or both in sequence, or neither, or...) As such, there's no obvious link between Mutant::bark and Tree::bark. Likewise, you don't rename Dog::bark; you create a new Mutant::yap that calls Dog::bark.
One thing that might help would be a trait for methods that tells us where it came from - that is, which - if any - of the composed methods it calls. For instance:
role Mutant does Dog does Tree { method bark() was Tree::bark; method yap() was Dog::bark; }
As I envision it, "was" sets things up so that you can query, e.g., Mutant::yap and find out that it's intended as a replacement for Dog::bark. Or you could ask the Mutant role for the method that replaces Dog::bark, and it would return Mutant::yap.
It also provides a default code block that does nothing more than to call Dog::bark; unless you override this with your own code block, the result is that Mutant::yap behaves exactly like Dog::bark.
By default, this is what other methods composed in from Dog do: they ask Mutant what Dog::bark is called these days, and then call that method. All that's left is to decide how to tell them to ask about Tree::bark instead, if that's what you want them to do.
On Wednesday 20 August 2008 15:16:12 Aristotle Pagaltzis wrote:
It therefore seems necessary to me to specify dispatch such that
method calls in the Dog role invoke the original Dog role methods
where such methods exist. There also needs to be a way for a
class that assumes a role to explicitly declare that it wants
to override that decision. Thus, by default, when you say that
Mutant does both Dog and Tree, Dog’s methods do not silently
mutate their semantics. You can cause them to do so, but you
should have to ask for that.
How much of this does compile-time role method collision detection provide? If Dog and Tree both provide the bark method, the performing entity must disambiguate them.
(I thought we had some syntax for "When invoked as a Dog, do this and when invoked as a Tree, do that" but I'm far too lazy to look that up at this moment.)
If you would like to report an abuse of our service, such as a spam message, please . Если Вы хотите пожаловаться на содержимое этой страницы, пожалуйста .