On 2009-Jul-10, at 4:37 pm, Jon Lang wrote:
This is one of the distinctions between role composition and class
inheritance. With class inheritance, the full tree of inherited
classes is publicly accessible; with role composition, the methods
of the combined role are the only ones that are made available to
the class.
OK, that's actually about what I was thinking, despite the peculiar
way I expressed it. I meant the full names to refer to methods
directly in the composed role, not somewhere else. Of course, there's
already a way to refer to methods with the same name -- using the long
name that includes the signature. So my example should have used
"bark(Canine: ...)" and "bark(Tree: ...)"; and whichever one actually
gets called depends on whether the invocant does Canine or does Tree.
so Dogwood::bark ought to consider its context (am I being called to
behave like a Canine, a Tree, or something else?) and decide what to
do based on that. If Dogwood::bark isn't defined, you should get an
implementation conflict error, because the class failed in its duty
to provide an implementation.
Yes, and Dogwood::bark could handle it by something like: "if
$self.does(Canine) {...} elsif $self.does(Tree) {...}" -- but Perl
already knows how to handle multiple dispatch based on type, so I
shouldn't have to write it out manually. In fact, this works with
Rakudo: you can have both barks if you declare them as multis, and
then it will accept them without having to declare a Dogwood::bark.
(But of course if you try calling it, you get an "Ambiguous dispatch
to multi 'bark'" error, because a $dogwood object equally satisfies
both signatures.)
(I tried to see what would happen if you cast the $dogwood object to
Canine or to Tree, but either Rakudo doesn't do it yet, or I got it
wrong.)
Needing to say "multi" makes sense if you wanted multiple methods of
the same name *within* a role (or class or any other namespace), but I
don't think it should be necessary across different Roles. Since they
already exist in different namespaces, we know they're supposed to
mean different things, and it's a simple fact of life that sometimes
the same term will get used in different places for completely
different meanings. If you have to do the dispatching manually, I
guess that's only a slight annoyance as long as it's possible. (Maybe
it's better to force the programmer to do it, not because Perl
couldn't, but to prevent potential surprises? Hm.)
role R { method foo() { say "foo" }
role R1 does R { method bar() { say "bar" }
role R2 does R { method baz() { say "baz" }
class C does R1 does R1 { }
The question is whether or not Rakudo is smart enough to realize
that R1::foo is the same as R2::foo, or if it complains that R1 and
R2 are both trying to supply implementations for foo. The former is
the desired behavior.
Conversely, in this case the same name means the same thing, so it
does seem perl ought to be able to tell that both foo's are really a
single foo() here; since they both come from the same role (R), they
have to mean the same thing, and C has to know that it does R.
In any case, then the question is how to know what role something
does, which is really a question about casting and passing args rather
than anything to do with Roles per se. I can't tell just from
"$dogwood.bark" which kind of barking is wanted; but I could have
Dogwood::bark_like_a_dog() instead, perhaps.
However, in
sub nighttime (Canine $rover) { $rover.bark if any(burglars()); }
I can only call ".bark" because all I know for sure is that I have
something which does Canine; if I pass it a $dogwood object, I see
three possibilities:
1) $rover in the sub is just the Dogwood object that was passed in,
and calling $rover.bark cannot know what to do. I also can't call
$rover.bark_like_a_dog or anything else, because that method exists
only for Dogwood objects, and the sub doesn't always receive
Dogwoods. So I'm stuck, and I don't see any way around that the way
things are.
2) $rover does Canine and only Canine -- the Tree-half of $dogwood
that was passed in is invisible inside the sub, and thus $rover.bark
calls bark(Canine:) which is what we want. (Of course, it calls
Dogwood's bark(Canine:) when passed a Dogwood object -- it's not
magically jumping back to the original Canine role.) If nighttime()
in turn calls something-else($rover), the something-else sub also gets
only a Canine object.
3) $rover acts like a Canine, but the rest of the original $dogwood
arg (the Tree parts) are still there; they just aren't used unless
somehow explicitly brought out; for example, by casting $rover to a
Tree, or by passing it to some other function that is looking for a
Tree object. This is how I'd like it to work, because that's the most
flexible.
Maybe there should be "hard casting" and "soft casting": by hard
casting I mean stripping out everything other than the requested Role/
Class, so that it's gone for good. I think that fits the usual idea
of casting or coercing an object to a different type. Soft casting
would be focussing on the requested Role and ignoring anything else
the object does, but still leaving the other roles available if you
really want them. Foo($x) would then do a "hard" cast, while passing
$x to sub(Foo) would merely soft-cast it.
-David