The key to understanding roles is to note that roles don't implement methods; classes implement methods. Roles define which methods must be implemented, and suggest ways that they might be implemented; classes decide which implementation to use. Anything that breaks this paradigm is a Bad Thing.
Where things get slightly fuzzy is that there is one case where a class is implicitly assumed to accept an implementation suggested by a role - namely, if that implementation is the only one available. Otherwise, the role is required to explicitly define a given method's implementation. David Green wrote: > Jonathan Worthington wrote: >> The spec is right in that you need to write a method in the class that >> decides what to do. This will look something like: >> >> method fuse() { self.Bomb::fuse() } > > That makes sense for using the combined role on its own, but can we still > handle separate roles that get mixed together? That is, after defining that > method so I can call $joke.fuse(), can I still call $joke.Bomb::fuse and > $joke.Spouse::fuse as well? 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. In effect, a combined role acts as a middle manager that looks over the available implementations and picks one, provides its own alternative, or defers the decision to its boss (i.e., the class or role into which it is composed). Either way, once the class has chosen an implementation, that is the implementation that will be used. As I understand it, the reason for this has more to do with attributes than with methods: with role composition, you want to be able to "cut away" any attributes that have become extraneous to the implementations defined in the class. E.g.: role R { has $foo; } class C does R { method foo() is rw { doIO() } } The idea here is that C has chosen to implement foo by querying an outside source (such as a database) whenever a read request is made of it, and by sending information to an outside source whenever a write request is made. It never refers to the internal state that R defined. As such, there's no reason for C to set aside memory to track an internal state. You can't do this if someone is allowed to explicitly call R::foo from any object of class C, overriding C's choice as to how foo should be implemented. > I'm thinking that it's useful to be able to refer to the fully-qualified > names for anything composed into a role or class; often there will be no > ambiguity, so the full name won't be necessary. If the names aren't unique, > then you can specify them fully, and perhaps add an unqualified "fuse()" > that does one or the other (or both? or neither??) for convenience. That > shouldn't be necessary, I think -- it just means you would have to spell out > exactly which method you wanted. This is "class inheritance" think. In "role composition" think, you should never have to worry about how the composed roles might have done things once composition is complete; you only concern yourself with how the class does things. > In the case Ovid also mentioned where two roles have a method of the same > name because both methods really are doing the same thing, there ought to be > a way to indicate that (though if they both come with implementations, you'd > still have to pick one or write your own). > > Of course, in a perfect world, the common method would come from its own > role: if Foo.fuse and Bar.fuse really mean Foo.Bomb::fuse and > Bar.Bomb::fuse, then doing Foo and Bar together should automatically give > you a single .fuse method (again, at least as far as the interface is > concerned). You have a point: it would be nice if you didn't have to engage in unnecessary conflict resolution. Of course, this may actually be the case already; it all depends on how the compiler decides when to complain about conflicting methods. 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. > I guess being able to create a role by dropping some bits from an existing > role would be useful sometimes, but it seems to lose one of the most useful > benefits of roles: as Jon Lang pointed out, "R1 :without<foo bar>" would > effectively be a new role, one that doesn't do R1. But you want something > to do a role in the first place so that it will work anywhere else there is > code looking for that role. Obviously, someone who explicitly drops methods from a role isn't concerned with the new role being usable wherever the original role could be used (although there might be times when he'll be concerned with the converse). This is actually a separate topic that shouldn't be conflated with the issue at hand; and I apologize for conflating it. If all you're trying to do is to avoid implementation conflicts, I'd recommend "C does :blocking<foo bar> R1" instead of "R1 :without<foo bar>": that approach would still insist that C implement foo and bar, letting you use a C anywhere an R1 is called for; but it would suppress R1's usual proposed implementations for foo and bar, avoiding any chance of its proposals conflicting with any others. Or perhaps it should be something along the lines of "Class does :accepting<foo bar> Role", meaning that Class must implement all of Role's methods, but will only consider Role's proposed implementations for foo and bar; and (if possible) "Class does :!accepting<foo bar> Role", meaning the same thing, except that the implementations of foo and bar are the only ones _not_ under consideration. (If :!accepting wouldn't work that way, go with something like :rejecting instead.) > So supposing: > > role Canine { method bark { say "ruff"; } }; > role Tree { method bark { say "rough" } }; > > class Dogwood does Canine does Tree { ... }; > my Dogwood $dw; > > sub nighttime (Canine $rover) { $rover.bark if any(burglars()); } > sub paper (Canine $rex) { $rex.bark if newspaper(:delivered); } > sub paper (Tree $nodes) { $nodes.bark ==> flatten; ... } > > What happens when I call nighttime($dw)? Obviously, it's meant to call > $dw.Canine::bark. Since nighttime() is looking for something that does the > Canine role, any method calls in that function can safely be assumed to be > short for .Canine::bark (since all we know for sure is that any arg passed > in will do Canine, and we can't know it will do anything else). > > If I want to call paper(), then I would have to cast $dw to either one of > the roles, e.g. paper(Tree($dw)), and that would presumably strip off or > hide the Canine part of its nature. It doesn't seem unreasonable for any > non-Tree attributes to be inaccessible inside paper(Tree), since all it can > guarantee is the arg does Tree; but on the other hand, I think it would be > OK but would require you to use the fully qualified names for anything > non-Tree. Again, this is class inheritance logic. If you want to do this sort of thing, you'd say: class Canine { method bark { say "ruff"; } }; class Tree { method bark { say "rough" } }; class Dogwood is Canine is Tree { ... }; my Dogwood $dw; sub nighttime (Canine $rover) { $rover.bark if any(burglars()); } sub paper (Canine $rex) { $rex.bark if newspaper(:delivered); } sub paper (Tree $nodes) { $nodes.bark ==> flatten; ... } In this scenario, I could see nighttime calling Canine::bark (since Dogwood::bark, the first choice, doesn't exist), and paper complaining of ambiguity unless $dw is explicitly cast to either Canine or Tree - although as written, I believe that the ambiguity is resolved by the fact that Canine came first in the definition of Dogwood. > If Dogwood defines its own .bark method, I can see it meaning one of two > things: either it's yet another bark(), specifically $dw.Dogwood::bark(), > that can be used only where we're expecting a Dogwood object. (It might > bear no relation to Canine::bark or Tree::bark, although in that case it > would probably be a good idea to pick a different name!) Or else, it could > mean a consolidation of the two mixed-in .bark's, i.e. $dw.Canine::bark and > $dw.Tree::bark would both now be implemented by plain $dw.bark, aka > $dw.Dogwood::bark (all three full names would mean the same thing for > Dogwood objects). For role composition, the first thing to note is that Canine::bark and Tree::bark are two very different things; 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. And if you _can't_ devise a Dogwood::bark method that will behave properly in both contexts, you should rethink the Dogwood class. -- Jonathan "Dataweaver" Lang