On 11 August 2016 at 09:25, Karen Etheridge <p...@froods.org> wrote: >> Moose roles have some limitations, such as inability to override a > method in a class which "with"es the role. > > This is false. For example: > > { > package MyRole; > use Moose::Role; > sub foo { > 'I came from the role'; > } > } > > { > package MyClass; > use Moose; > with 'MyRole'; > around foo => sub { > 'I came from the class'; > }; > }
In fact, the above inherent conflict requiring explicit handling is seen as a "feature" of Moose Roles: It prohibits accidentally overriding classes in an "external" context, and makes it glaringly apparent that a given sub is an overrride. With standard inheritance, you have to use tools to introspect the @ISA graph at runtime[1] and *then* you know which methods are overridden and where they parent to. The simplest description I have of Roles in advantage to Inheritance is their design makes it easier to reason about what it is your code does, it makes it clearer from a developers perspective what the composition is, and simplifies the logic required for deep composition. Because unlike Multiple inheritance where the composition is performed dynamically by the perl interpreter at runtime, with arbitrary depth @ISA's, Role composition is done in layers, where each layer is composed and made immutable prior to being composed into the next. This is very different from MI, where the default intuition you'll apply to reason about "what method is the next one" is pretty much wrong, because counter-intuitively, if you mentally build a method resolution order for a given sub in a given class .... then compose that given class into another, instead of the method resolution of its resulting class being trivially represented in terms of its parts, it is in reality unique to its parent, because MRO's are hard. For example: CHILD2::foo CHILD1 isa CHILD2 CHILD1->foo therefore calls CHILD2::foo You'd probably be prone to assuming that because of that: PARENT isa CHILD1 , CHILD3 PARENT->foo therefore would call CHILD2->foo But it might not. It might call CHILD3::foo, or CHILD4::foo , depending on what CHILD3 does. ^^^ this stuff does my head in and I've spent quite a few hours bashing my head over understanding exactly how it works, and even my example case is probably wrong in some regard. But the truism is that in large hierarchies, mro::get_linear_isa(PARENT) and mro::get_linear_isa(CHILD1) can very much be very different from each other. And then how they're different depends on which mro is assigned to each class. For producing a MRO for PARENT, /only/ the MRO assigned to PARENT is considered, even when composing its parents. When producing a MRO for CHILD, /only/ the MRO assigned to the CHILD is considered, even when composing its parents. And this stuff will make your head hurt and your code confusing unless you have reams of tools at your disposal to answer such questions, and you know to use the tools. Roles are, by comparison, much more intuitive. But they come at a technical price: Lots of anonymous subs all over the place gluing everything together. Which makes understanding traversal *from code* harder, and makes introspection tools somewhat "blind" to the dark magic that is going on. I'd probably be inclined to argue that neither MI or Roles are very fun to use, and they're more often abused than used, so if you feel yourself reaching for them, ask yourself "do I really need to?". And I'd be inclined to argue maybe you should consider using attributes with delegation instead of roles and inheritance. That choice has its own limitations of course, notably the additional indirection comes at a performance price. But it also does away with the sorts of crazy people tend to end up asking for: Instance Roles. Because its much nicer to just replace an attribute with an object of a different class than it is to apply a role to an instance to achieve the same effect :) hth -- Kent KENTNL - https://metacpan.org/author/KENTNL