Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Wednesday March 18, 2009
09:35 AM

Role Oriented Programming

[ #38662 ]

I've missed something, I'm sure, but here's a brief explanation and a question at the end.

The more I play with refactoring with roles, the more surprises await me. Eliminating inheritance for roles is ridiculously easy. As it turns out, the only hard parts of this switch have been those bits of code where our multiple inheritance has provided "extraneous" methods and the roles fail in curious ways. Thus, we get exposure of some design flaws for free (but it's a far too much of a digression to describe right now).

Compare:

package Our::TV::Episode;

# assume these use multiple inheritance, too
use base qw(
  Audit
  Tags
);

Versus:

package Our::TV::Episode;
use Moose;
extends 'Parent';
with qw(
  DoesAuditable
  DoesEditable
  DoesTagging
  DoesRelatedLinks
);

We've flattened the hierarchy, we see all behaviors our class has and we see that many of these items clearly didn't belong in an "isa" relationship in the first place. Is this a good strategy? At first, I was tempted to say "not really, but it's better than the inheritance mess". However, I'm not sure that I still see this as an interim strategy. This might be a better design methodology. Perhaps even the base class can be eliminated. Every object would be composed of roles, with explicit method conflict resolution at compile time and instead of spaghetti code or ravioli code, perhaps we could think of this as "dim sum code", with every object being created via a tasty pick and choose menu of exactly what that object needs and nothing more.

Now you might look at the above and say "but tagging, auditing and that other stuff aren't part of a TV episode either! They don't belong there any more than they belonged in the inheritance tree." In real life that sounds reasonable, but code is written to fit our business needs first and only incidentally to model the real world. For example, as far as the BBC is concerned, even the most rabid Doctor Who fan has never watched an episode of Doctor Who in their life. You see, episodes have versions (e.g., "edited for adult content"), but you've never seen a version, either. Versions are broadcast at a set time or available on demand via iPlayer or similar technology. So you've not seen an episode, you've seen a broadcast or an ondemand. In the real world, you don't really think about all of this and you shouldn't. You don't need to know that brands have series and series might have more series and those have episodes and they all need to be audited, and tagged, and grouped into franchises and have rights modelling and so on and so on. You just want to watch Doctor Who. However, what I need to do to model your episode for our business needs and they don't always fit your Friday night on the sofa with two pints of lager and a packet of crisps.

Moving along, were I to eliminate base classes, that would also eliminate this silliness:

sub foo {
    my $proto = shift;
    my $class = ref $proto || $proto;
    die "You must override 'foo' in $class";
}

That fires at runtime, not compile time. Just adding a requires qw'foo'; to your role and this becomes a compile-time failure.

My question: what's wrong with this approach? It's so easy, so obvious and makes understanding code much easier that I can't see why I shouldn't go all the way with this. I'm throwing caution to the wind, so it would be nice if you'd throw some caution back at me.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • Flattened, composable behavior -- sounds like traits [unibe.ch].

    ;-)

    -- dagolden

    • I'm pretty sure that Stevan read some (or all) of those papers before working on roles in Moose.

      • I was sort of making fun of Ovid, since he wrote Class::Trait.

        -- dagolden

        • Actually, while I wrote the first full-featured trait implementation that I'm aware of (though after others tried their hands at it), but it's Stevan who wrote Class::Trait. I just took over maintenance because I kept sending in bugs reports :)

    • I've read through that a number of times and I think they're right. There are, however, a couple of key differences. First, they don't have state with their traits (see bottom of page two in Traits: Composable Units of Behaviour [unibe.ch]). I think that's a mistake and so does Perl 6 and Moose. Smalltalk traits only share behavior, not data.

      Second, they're not advocating eliminating inheritance. They're advocating eliminating MI and mixins and using traits for allomorphic properties. I'm thinking that perhaps t

      • I agree the local class overriding role can seem confusing, but over the past few years of using roles pretty heavily I have come to appreciate this feature quite a lot. It actually makes the roles more re-usable since it is very easy to locally override something. Yes it does destroy some of the black-box-ness of Roles, but honestly I have not found roles to be very useful unless you can look inside and see what they provide/do. I have come to kind of see Roles as being more semi-transparent then opaque,

        • But for the few times that a local class needs to override a role, why not just have the class explicitly exclude that method from the role? Same effect, but it's explicit, not hidden. So you avoid mysterious breakage and you get closer to how roles were originally intended to be used (a good thing, in this case).

          I realize you probably can't change the API now, but what about use Moose::Role 'strict' or something like that?

          • What do you mean by "how roles were originally intended"? Because the traits papers describe local class overriding roles pretty specifically.

            - Stevan

          • [Why] not just have the class explicitly exclude that method from the role?

            That's for the same reason that classes don't explicitly mark overridden methods as "Hey! I'm overriding this method here! Pay attention! Don't look in my superclass! I'm over heeeeere! CALLMECALLMECALLMECALLME!"

            I can imagine a debugging or introspection mode where you can see exactly where methods and state come from, but one of the goals of roles was to provide transparent and typeful class componentization. If you don't kn

            • That's for the same reason that classes don't explicitly mark overridden methods as "Hey! I'm overriding this method here! Pay attention! Don't look in my superclass! I'm over heeeeere! CALLMECALLMECALLMECALLME!"

              We know they don't do this in Perl, but we also know that Perl's core OO is fairly limited. In several other languages, you must explicitly mark overridden methods. It's a great help to the programmer to see what's going on. After all, programs should be written primarily for humans to read and only incidentally for computers to understand.

              If you don't know which methods a role provides, you shouldn't use it.

              Agreed. There's still a difference between the real world and the desired world and in the real world, things aren't that simple. I want my code to fail at compile t

              • So far, aside from mocking my point of view, I've not heard anything which says that silently ignoring the role's method has a lower cost than the programmer explicitly ignoring it.

                Let me put it forth then: requiring explicit confirmation of overriding would be inconsistent with the existing stages of implicit overriding. I'll even raise the question of consistency in a context unrelated to OO: do you worry that the binding of lexical variable declared in an innermost scope shadows a lexical variable of

                • do you worry that the binding of lexical variable declared in an innermost scope shadows a lexical variable of the same name declared in an outer scope, and does so without an explicit marking?

                  Comparing a lexical variable and and a global method definition is comparing apples to oranges. When I'm calling $object->foo, I neither care, nor worry, what the lexicals are. I also don't worry about how &foo is defined, but I should care. And I do. With my heavy use of roles, I've found (an anecdote, I confess) that silently overriding a role method is a painful bug to track down. Knowing that, unlike with inheritance, we can actually find and prevent such errors at compile time but the keepe

                  • With my heavy use of roles, I've found (an anecdote, I confess) that silently overriding a role method is a painful bug to track down. Knowing that, unlike with inheritance, we can actually find and prevent such errors at compile time but the keepers of the keys refuse to do so doesn't make my code magically work.

                    One of the advantages of a slick IDE would be that you could trap many of these mistakes at edit time even, rather than compile time. Purple method-names are overriding something, blue aren't (or whatever).

                    One of the things you get for free with Smalltalk's image system I suppose.

                  • Comparing a lexical variable and and a global method definition is comparing apples to oranges.

                    I don't know what a "global method definition" is. Method definitions have their own scoping; they're scoped to invocants of the appropriate type. That's why I believe that the shadowing rule is appropriate: we're talking about which piece of code or behavior Perl can access when you use a specific name in a specific context.

                    Knowing that, unlike with inheritance, we can actually find and prevent such errors a

        • I thought that part of the point of having traits flatten at compile time was to require that kind of override to have explicit disambiguation. So you can locally override, but it's called out in the code.

          Maybe a bad example, but for the sake of debate:

          {
              package Bar;
              use Moose;
              with 'Foo' except 'doit';
              sub doit { print 'Bar' }
          }

          Excluding 'doit' from Foo without providing it in Bar would also be an error, of course.

          -- dagolden

          • When traits/roles are applied to classes the local class overrides the role, but the role overrides the superclass. When traits/roles are applied to other traits/roles they are merged together into a composite role and that is where this kind of disambiguation is needed. This cookbook recipe [cpan.org] shows both aliasing and exclusion of role methods, this is a feature directly taken from the traits papers but which Perl 6 has not decided to include.

            - Stevan

            • This cookbook recipe shows both aliasing and exclusion of role methods, this is a feature directly taken from the traits papers but which Perl 6 has not decided to include.

              You know, I was wondering why I hadn't seen that in Perl 6. What does that last bit mean, though? That Perl 6 is not yet decided or that they've decided not to offer this? (I'll try and reread the spec tomorrow)

              • Perl 6 does not provide ways to explictly exclude or alias methods during compsition (well not that I have seen anyway, cause I don't follow it much these days).

                - Stevan

  • I know I keep saying this - but this _really_ reminds me of the way Eiffel puts things together (although they keep the class metaphor rather than calling them roles).

    • Except that you still have MI (but with explicit method resolution) and you still can have behavior buried in inherited classes rather than explicitly stated in the preamble to your code (though I love that you must explicitly override them). Also, because it's class based, it's not as easy to share behavior amongst classes which are not structurally related -- one of the design goals of roles.

      That being said, Eiffel looks really nice and if more than 3 people were using it ... ;)

    • This book [amazon.com] is actually one of my all time favorites. Eiffel has a nice concept of partial classes which themselves can require methods to be implemented, but it does not have conflict resolution like roles do so you are still susceptible to some of the issues that traditional MI/mixins has. But actually, if you throw in the rename and redefine keywords in Eiffel and you can actually manually fix that if you want. TIMTOWTDI I guess :)

      - Stevan

  • Moving along, were I to eliminate base classes, that would also eliminate this silliness:

            sub foo {
                    my $proto = shift;
                    my $class = ref $proto || $proto;
                    die "You must override 'foo' in $class";
            }

    That fires at runtime, not compile time. Just adding a requires qw'foo'; to your role and thi

    • For a variety of reasons they're significantly different. Amongst other issues, C++ and Java interfaces provide no implementation, nor do they offer conflict resolution. See the link dagolden provided for an in depth tour of roles (known also as "traits").

      • That question makes it sound like interfaces in C++ and Java actually work. (I suppose they work as designed, which makes them that much worse.)