I'm sorry, but this statement is wrong in too many ways to enumerate here.
Modern Perl refers to simply learning from one's past and eschewing a number of practices that have turned out to be bad. As a few examples (this is *by far* not an exhaustive list):
- programming without strict/warnings
- programming without using cpan (i.e. lots of reimplementation and copy/paste)
- write code that's difficult to read
- using bareword filehandles
- using 2-arg open
Modern Perl instead encourages people to (again, non-exhaustive):
- use strictures and warnings everywhere
- use CPAN as much as possible
- don't reimplement functionality repeatedly
- don't ever copy-paste code
- write tests for everything
- use Devel::Cover, Perl::Critic, Perl::Tidy
- use modern features of perl core (such as 3-arg open, filehandle references), that replace bad practices
https://github.com/stevan/p5-mop-redux/blob/master/lib/mop/traits.pm
It looks like the trait implementations get passed the metaclass as the first argument. So it seems like you could modify other attributes and methods that belong to that metaclass.
]]>As laylward pointed out, you get full access to the metaclass and then some extra info pointing to the specific thing it was applied too (attribute name, etc).
In Perl 6 they handle all this by having multi-subs, I plan on figuring something out to allow us to do something similar without multi-subs. Most likely it will be something in the arguments to the traits functions that will help indicate upon what it was applied. That way you could have a single "rw" trait that would be able to be applied to multiple metaobjects (attributes, methods, classes, whatever). Where this will get tricky is extending an existing trait, but in that case there is always the old Moose style road to take as well, nothing about this removes that feature.
]]>I would expect it to result in some metaclass method being called.
]]>I would expect it to result in some metaclass method being called.
But then you need to create a subclass of the metaclass which implements the method in question. And if you've got multiple traits, then normal subclasses are probably out of the question, and you need to instead build a subclass by composing various roles. This is what people do with Moose, and it's one of the things Stevan's trying to avoid for p5-mop.
That said, the sub that gets called does get passed the metaclass as an argument, so it may apply a role to it, or call a method on it, or whatever.
]]>@leont: your dist with two wishlist tickets only no longer appears. You have other dists which do appear though ...
@rkinyon: if you give HANDOFF or ADOPTME permissions on your modules, that will give your unloved dists a +1. They're scoring low though, so with my new version they still wouldn't make the cut. I'm thinking about another column for "adoptability", and also including modules that score high on that, even if the current score doesn't pass the threshold.
Consider the non-inheritance relationship, where one object delegates to another. You can't get the right answer about a type question by asking "Does the delegator inherit from the delegatee?", and you know that if you limit all of your questions about object behavior to "Does this object have a method of this name?" then you run into the false cognate problem (a tree has bark while a dog can bark; a student can bomb a test while a fighter jet can bomb a test target).
What you need is a way of asking "What named collection of behaviors does this entity provide?" without running into the false cognate problem and without dictating the nature of relationship between the entity in question and whatever you think provides that named collection of behaviors.
By consuming a role, you promise that the consumer provides the named behavior defined by that role. Consuming a role often flattens one or more methods into the consumer, but if the consumer already provides those methods, the flattening doesn't have to happen.
Roles give you a way of grouping collections of attributes and behavior under names as well as a default mechanism to provide that collection of behavior in whole or in part to a consumer. That's it.
]]>Avoid complex inheritance hierarchies. This is equivalent to only allowing one level of inheritance.
Yes, but then in order to do non-trivial specialisation you have to start inheriting from multiple things, like you would compose multiple roles. And then you run into problems because multiple superclasses can provide the same method – which one do you call? Do you call both? Do the two methods on those two superclasses mean the same thing?
But with roles you do not have this problem. Why? Because roles become part of the class that consumes them, and you cannot compose multiple roles into a class if they provide methods of the same name (you will get an error at compile time) – not without explicitly disambiguating them.
Besides, roles can use other roles (at least they can in Roles::Tiny), so this problem is not avoided by using roles.
Yes, but it is not a problem to have that kind of hierarchy. Why? Same reason: because roles are not inherited by other roles, they are composed into them – again detecting errors at compile time and demanding explicit disambiguation.
Better encapsulation and code re-use. To find if a class does something, you just use the UNIVERSAL->can method. To find if a class has inherited a particular class, use the isa method. This doesn’t seem different to roles.
What this bit mostly means is that because you do not inherit from roles, they can be used like interfaces in Java: you declare a bunch of requirements the consuming class must fulfil, and then someone else can specify the interface instead of a class where they say what type they expect, which works because any class implementing the interface, well, implements the interface. This was Java’s attempt to provide something like multiple inheritance, without providing actual (dangerous) multiple inheritance.
Roles are better because they can include default implementations – with Java-style interfaces, every class conforming to an interface is forced to contain a separate implementation of it, even if it’s boilerplate.
Obviously multiple inheritance doesn’t have that problem. Instead, it has other problems – much much worse problems – problems that roles also don’t have.
So basically:
Think of roles as multiple inheritance without the inheritance – something that gives you the benefits that multiple inheritance promises, except through a different mechanism that works without the indirection of inheritance, and therefore inherently lacks the method dispatch clusterfuck potential of complex MI hierarchies: MI without the diamond problem.
(As a side benefit, because you have two different kinds of entities (classes and roles), you get a clear distinction between concrete entities that can be instantiated (classes) vs conceptual ones that can’t (roles). If you use MI in the same style, everything is just classes, just some of them happen to be abstract, and there’s nothing inherently keeping you from trying to instantiate those, even though it’s useless (and if it works (which it may), a bug).)
]]>One example that I have seen about roles is GuardDog class example: in Dog class hierarchy, and consuming Guard role (which can be consumed by many different unrelated classes).
]]>