FAQ

Jonathan Worthington wrote in "YAPC::EU and Perl 6 Roles":
More fitting to me would be an adverb to the does trait modifier...

class C does R1 :without<foo bar> does R2 :without<baz> { ... }

The thing is that in this case, does the class actually do R1 and R2? If you
are going to derive an anonymous role with the methods missing, then answer
is "no". That is, C ~~ R1 would be false. So I think it'd need to act as a
modifier to the action of composition, rather than a modification to the
thing that we're composing.
Moving the adverb from the role to the trait modifier would still
violate the notion that C ~~ R1; so it doesn't actually gain us
anything. Instead, consider the following possibility: "R1
:without<foo bar>" is a separate but related role from "R1". The
implicit relationship between them is that R1 ~~ R1 :without<foo bar>.
So "C ~~ R1" would be false; but "C ~~ R1 :without<foo bar>" would be
true.
I wonder if we'd want to mandate that a method of the name must come from
_somewhere_ otherwise it's an error. At least then you get a promise that a
method of that name exists...which is about all that "it does this role"
tells you as an interface contract anyway.
Right. Another way to handle this without establishing "reverse-does"
relationships that I describe above would be to say that the adverb
doesn't actually remove the method in question; it just suppresses its
implementation. That is, given:

role R1 {
method foo() { say "foo" }
}

class C does :blocking<foo> R1 { ... }

this would compose R1 into C, but would discard the implementation of
R1::foo while doing so.

In the spirit of TIMTOWTDI, both approaches could be available:

"R1 :without<foo>" acts as an anonymous role that R1 implicitly does,
and which is set up almost exactly like R1 except that it doesn't have
method foo. This could have other uses besides role composition, such
as expanding a web of roles from the specific to the general.

"C does :blocking<foo> R1" composes R1 into C, but discards foo's
implementation while doing so.
Alternatively, I could see a version of this exclusionary policy being
done through method delegation, by means of the whatever splat -
something like:

class C {
has A $a handles * - (foo, bar);
has B $b handles * - baz;
}
The RHS of the handles is something we smart-match the method name against
(unless it's one of the special syntactic cases). And thus if you care about
performance you probably don't want to be falling back to handles to do your
role composition, since it's kind of the "last resort" after we've walked
the MRO and found nothing.
You're right, but for a different reason. Perl 6 has three techniques
for code reuse, which I refer to as the "be, do, have" triad:

Class inheritance ("to be"): 'is C;'
Role composition ("to do"): 'does R;'
Attribute delegation ("to have"): 'has A $a handles foo;'

Just on a conceptual level, you don't want to fall back on attribute
delegation in order to emulate role composition. Still, there are
uses for delegating to all or most of an attribute's methods.
Anyway, you'd put something on the RHS maybe
like:

has A $a handles none(<foo bar>)

But I'm not sure that will fall through to B for anything that A doesn't
define other than those two. You'd perhaps just get a dispatch error if you
said A handles everything but those and it didn't. So it'd probably look
more like...

has A $.a handles all(any(A.^methods>>.name), none(<foo bar>));

Which you almost certainly don't want to be writing. ;-)
Right; which is why I was looking for a more abbreviated form. If we
go with the idea that we're using a Set on the RHS, then '*' could be
shorthand for 'Set($a.^methods)', and '&Set::infix:<->' could be the
Set Difference operator, with the item, list, or set on the RHS being
excluded from the LHS. So:

$a handles * - <foo bar>

would be short for something like:

$a handles Set($a.^methods) - Set("foo", "bar")

This use of the Whatever splat is similar to its use in a list index,
where '*' behaves as if it were the list's element count.

--

Looking this over, I note that the only code reuse mechanism for which
we haven't looked at the "everything except..." concept is class
inheritance. OTOH, I think that the blocking tool that works for role
composition could be adapted for use with class inheritence as well:

Class C is :blocking<foo bar> C1 is :blocking<baz> C2 { ... }

That is, if you were to search the class heierarchy for foo, it would
skip the C1 branch in its search. This would have to be treated with
care, because it would mean that C doesn't inherit everything from C1.
This _would_ break the "anything you can do I can do, too" nature of
class inheritence; but I'm not sure how big of a crime that is. Perl
generally discourages the use of class hierarchies, and it certainly
isn't the preferred means of type-checking. And maybe you could get
around this by having the "isa" predicate return a more involved
response than merely "true" or "false" - say, an object that "is true"
but which can be queried for the exceptions:

$test = C isa C1;
if $test { say "they match!" } # output: "they match!"
@exceptions = $test.blocking # one-item list: (C1:<foo bar>)

In short, "C isa C1" would answer "yes, except..."

--
Jonathan "Dataweaver" Lang

Search Discussions

  • David Green at Jul 10, 2009 at 5:13 pm

    On 2009-Jul-7, at 5:28 am, 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?

    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.

    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).


    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.

    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.

    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).



    -David
  • Jon Lang at Jul 10, 2009 at 10:37 pm
    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
  • David Green at Jul 13, 2009 at 12:15 am

    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
  • Brandon S. Allbery KF8NH at Jul 13, 2009 at 12:32 am

    On Jul 12, 2009, at 20:15 , David Green wrote:
    sub nighttime (Canine $rover) { $rover.bark if any(burglars()); } (...)
    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.
    If you haven't declared it as such, this strikes me as a bad thing.
    Perhaps some kind of declarative syntax that lets you declare that you
    can take a Dogwood, such that you get an added argument which is undef
    (for a non-Dogwood) or a Dogwood (or Tree?)?

    --
    brandon s. allbery [solaris,freebsd,perl,pugs,haskell] allbery@kf8nh.com
    system administrator [openafs,heimdal,too many hats] allbery@ece.cmu.edu
    electrical and computer engineering, carnegie mellon university KF8NH
  • Jon Lang at Jul 10, 2009 at 10:39 pm
    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
  • Daniel Ruoso at Jul 12, 2009 at 6:41 pm

    Em Sex, 2009-07-10 às 15:39 -0700, Jon Lang escreveu:
    The key to understanding roles is to note that roles don't implement
    methods; classes implement methods.
    Er, while I see your point, Roles are not just interfaces... they are OO
    components that can be plugged into other classes. They often are used
    for type identity exactly because of that attribute, since you won't be
    enforcing any hierarchy.
    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.
    That's not the common conception in Roles usage, specially in Moose. As
    I said, Roles are not just interfaces, they are OO reuseable components.
    The spec itself says:

    Classes are primarily for instance management, not code reuse.
    Consider using C<roles> when you simply want to factor out
    common code.

    The key issue here is Perl 6 wasn't yet used to the extent that
    Moose::Roles are, and Moose people have identified that the use of Roles
    as reusable components raised the issue when the class inadvertedly
    overrides one of the methods that are implemented by one of the composed
    roles.

    I did think that this should be the expected behavior, but when the
    people that is heavily using it says "it took me a lot of time to
    debug", it indicates that there's something wrong with the behavior.

    So now I changed my mind, inheritance is about overriding behavior, so
    when you implement a method in the subclass it is a natural thinking
    that this should override the superclass, but when you think about it
    really carefully this logic doesn't really map well to Roles
    (considering roles as OO reuseable components).

    That being said, I'd think the following as an interesting solution:

    role R1 {
    method foo() {...} # degenerates to interface
    }
    role R2 does R1 {
    method bar() {
    # some implementation
    }
    method baz() {
    # some implementation
    }
    }

    class Bla does R2 {
    method foo {
    # implementing here is natural, since the role only
    # declared a stub, it's even a warning not to implement it
    }
    supersede method bar {
    # explicitly tells that I want to ignore the implementation
    # in the role. nextsame wouldn't find the role implementation.
    }
    augment method baz {
    # explicitly tells that I want to provide an additional
    # implementation besides the one in the role. nextsame would find
    # the role implementation.
    }
    }

    In the above example, declaring a method without either "supersede" or
    "augment" would result in a compile-time warning, while using "augment"
    semantics by default.

    dainel
  • David Green at Jul 13, 2009 at 12:29 am

    On 2009-Jul-12, at 12:43 pm, Daniel Ruoso wrote:
    role R1 {
    method foo() {...} # degenerates to interface
    }
    Just wondering: since merely declaring an interface will be common
    enough, should we be able to say simply "method foo;" inside a role,
    and drop the {...}?
    class Bla does R2 {
    method foo {
    # implementing here is natural, since the role only
    # declared a stub, it's even a warning not to implement it
    }
    supersede method bar {
    # explicitly tells that I want to ignore the implementation
    # in the role. nextsame wouldn't find the role implementation.
    }
    augment method baz {
    # explicitly tells that I want to provide an additional
    # implementation besides the one in the role. nextsame would find
    # the role implementation.
    }
    }
    Works for me. I thought having "suggest" to make it work the other
    way around sounded useful too, but perhaps you think in practice it
    wouldn't be worth it?


    -David
  • Jon Lang at Jul 13, 2009 at 1:37 am

    Daniel Ruoso wrote:
    Jon Lang wrote:
    The key to understanding roles is to note that roles don't implement
    methods; classes implement methods.
    Er, while I see your point, Roles are not just interfaces... they are OO
    components that can be plugged into other classes. They often are used
    for type identity exactly because of that attribute, since you won't be
    enforcing any hierarchy.
    Right. But as they were originally conceived, they were interfaces
    that could also handle code reuse, rather than units of code reuse
    that could also be used as interfaces. From this perspective, it
    makes perfect sense that a role's methods can be overridden as easily
    as they are.

    But you make a good point: there are some (a few? most?) programmers
    who are going to want to use roles primarily for code reuse, and who
    will want it to be a little more difficult to override the code
    provided by a role (e.g., requiring the use of supersede and perhaps
    augment in order to replace the definition with a new one). First and
    foremost, this distinction between suggested ans mandatory
    implementation is what I was trying to make a little more explicit in
    my proposal: a suggested method can be overridden by the class with no
    extra effort; a mandatory method requires that the class be explicit
    about the override.

    The next question is which of these approaches Perl 6 should use with
    roles. Currently, it's using suggested implementations; what I'm
    hearing you say is that you'd rather have mandatory implementations.
    IMHO, there's a time ans place for both; so I was trying to come up
    with a compromise of sorts: a way of letting the programmer select the
    approach that most suits his needs.
    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.
    That's not the common conception in Roles usage, specially in Moose. As
    I said, Roles are not just interfaces, they are OO reuseable components.
    FWIW, I never said that they're "just" interfaces. Also, I question
    whether that is or is not the common conception of role usage. I
    readily admit that it isn't so in the programming circles that you
    travel in; but are you typical of the perl community in this regard?
    This is not a rhetorical question; the way that we end up addressing
    this issue hinges on this question: should roles provide suggested
    implementations by default, or should they provide mandatory
    implementations by default? Even if Perl is rich enough to provide
    for both, the decision of which way to go when no explicit decision
    has been made is an important one.
    The spec itself says:

    Classes are primarily for instance management, not code reuse.
    Consider using C<roles> when you simply want to factor out
    common code.
    Right: roles are preferable to classes when it comes to code reuse.
    That doesn't necessarily mean that roles are _primarily_ intended for
    code reuse. They _might_ be; but if so, it's because they've grown
    beyond their original concept.
    The key issue here is Perl 6 wasn't yet used to the extent that
    Moose::Roles are, and Moose people have identified that the use of Roles
    as reusable components raised the issue when the class inadvertedly
    overrides one of the methods that are implemented by one of the composed
    roles.
    You know what? Until Moose was mentioned in this conversation, I had
    never heard of it.
    I did think that this should be the expected behavior, but when the
    people that is heavily using it says "it took me a lot of time to
    debug", it indicates that there's something wrong with the behavior.

    So now I changed my mind, inheritance is about overriding behavior, so
    when you implement a method in the subclass it is a natural thinking
    that this should override the superclass, but when you think about it
    really carefully this logic doesn't really map well to Roles
    (considering roles as OO reuseable components).
    That may indeed be the case. It's entirely possible that we may want
    to change things so that roles define mandated methods, and possibly
    introduce interfaces as a variation of roles that define suggested
    methods. But we may instead want to keep roles as they are, and
    define some other variation that works just like a role except that it
    mandates its methods.

    And its also possible that I'm fundamentally wrong about this, and
    that we _don't_ need both approaches available for roles.
    That being said, I'd think the following as an interesting solution:

    role R1 {
    method foo() {...} # degenerates to interface
    }
    role R2 does R1 {
    method bar() {
    # some implementation
    }
    method baz() {
    # some implementation
    }
    }

    class Bla does R2 {
    method foo {
    # implementing here is natural, since the role only
    # declared a stub, it's even a warning not to implement it
    }
    supersede method bar  {
    # explicitly tells that I want to ignore the implementation
    # in the role. nextsame wouldn't find the role implementation.
    }
    augment method baz {
    # explicitly tells that I want to provide an additional
    # implementation besides the one in the role. nextsame would find
    # the role implementation.
    }
    }

    In the above example, declaring a method without either "supersede" or
    "augment" would result in a compile-time warning, while using "augment"
    semantics by default.
    I'm not sure I follow the bit about "augment"; but I agree that
    something like this would work well when the desired behavior for a
    role is to provide mandatory implementations for its methods (or, more
    specifically, when the desired behavior for a method is to resist
    being implicitly overridden).

    --
    Jonathan "Dataweaver" Lang
  • Ovid at Jul 13, 2009 at 9:14 am

    ----- Original Message ----
    From: Jon Lang <dataweaver@gmail.com>
    Right. But as they were originally conceived, they were interfaces
    that could also handle code reuse, rather than units of code reuse
    that could also be used as interfaces. From this perspective, it
    makes perfect sense that a role's methods can be overridden as easily
    as they are.
    As originally conceived in Perl 6 or in the original traits papers? In the original research, the purpose of roles was to allow the decoupling of responsibility and behavior (code reuse) found in inheritance-based OO systems. Traits (roles) took over code reuse.
    But you make a good point: there are some (a few? most?) programmers
    who are going to want to use roles primarily for code reuse, and who
    will want it to be a little more difficult to override the code
    provided by a role (e.g., requiring the use of supersede and perhaps
    augment in order to replace the definition with a new one).
    Just to give people some real data to play with (our system may not be representative), here's some sample source code and some imformation about our use of roles in the BBC.

    package PIPs::ResultSource::Series;
    use Moose;
    extends 'PIPs::ResultSourceBase::BrandSeries';
    with qw(
    PIPs::ResultSource::Role::DoesParentChildRelationships
    PIPs::ResultSource::Role::DoesTags
    PIPs::ResultSource::Role::DoesContentObject
    PIPs::ResultSource::Role::DoesInspector
    PIPs::ResultSource::Role::DoesRelatedLinks
    PIPs::ResultSource::Role::DoesIdentifiers
    PIPs::ResultSource::Role::DoesChangeEvents
    );


    (The astute reader will not that the base class is awful, but it's been a long, hard slog to get this far).

    Most of our classes which implement roles have similar preambles, but with different behaviors listed.

    Other points of interest. Only 11 of 114 classes which implement roles exclude any methods (none use method aliasing) and we currently have 40 roles implemented.

    Only three classes provide methods which override role's methods, but in the few cases they do, we explicitly exclude the methods from the role to make it clear that we need to do this. We had more overriding of role's methods, but continual refactoring has pushed those into roles.


    So we're very, very heavily on the "use roles for shared behavior" side. The relative paucity of overridden role methods suggests to me that (for our code), the annoyance of having to be explicit for overridding a role's methods easily offset by how hard it's been to debug this issue. That being said, the pain in debugging might have been a side effect of the fast transformation from a complex inheritance hierarchy to a roles-based system.

    Cheers,
    Ovid

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupperl6-language @
categoriesperl
postedJul 8, '09 at 6:17p
activeJul 13, '09 at 9:14a
posts10
users5
websiteperl6.org

People

Translate

site design / logo © 2021 Grokbase