On Wed, Aug 20, 2008 at 3:16 PM, Aristotle Pagaltzis <[EMAIL PROTECTED]> 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 [i]doesn't[/i]
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.

Jonathan "Dataweaver" Lang

Reply via email to