Hey Ovid,

On Jul 15, 2009, at 2:24 PM, Ovid wrote:
On the Perl 6 language list, Raphael Descamps posted a link to a paper explaining how to implement stateful traits (http://scg.unibe.ch/archive/papers/Berg07eStatefulTraits.pdf ). One of the authors of that paper worked on the original traits paper and the research appears solid.

I was actually approached by the authors of this paper before it was published but after they had finished the implementation. They were wondering how we handled attribute conflicts in Perl 6/Moose. In Moose we will die on an attribute conflict, this is basically punting because the this problem is very hard. I explained to them though that Moose attributes were significantly more complex then Smalltalk attributes so our more extreme solution was not really applicable to them. Moose attributes are truly first class citizens of the MOP layer while Smalltalk simply has a list of slots in the instance and accessor methods that are (IIRC) totally unrelated to them on any formal level.

See the problem is that we (Moose) don't just have reader/writer methods to worry about, but we have reader/writer or accessor, predicate methods, clearer methods, associated builder and lazy_build methods and then the possibility of delegation methods with handles. The result is that we have a *LOT* of things to check, and we don't want to just test this once on creation of the attribute, but we actually need to compare the attribute to *every other attribute* the class (and it's superclasses) have. This is the only way to be sure that we are not creating a conflict with a method that is somehow tied to an attribute and therefore to state.

 Basically, it argues that traits need to have private accessors.

Yeah, this is something I remember discussing with chromatic, Luke Palmer and Audrey at YAPC::NA in Toronto. We kept going around and around on this never coming to a totally satisfying solution. It basically always felt (to me anyway) that it was hiding the problem instead of actually dealing with it. Although I have not read the paper yet, so perhaps when i see their reasoning I might feel different.

Also note that (at least some) of the authors or this paper expressed to me that they were not happy with this outcome. That having privacy in Smalltalk felt very un-Smalltalk-ish and so the solution kind of felt dirty. I feel the same way about this in regards to Perl, the idea of requiring privacy seems very un-Perlish.

I won't go into full detail (you'll have to read the paper), but I decided to see exactly how Moose handles this. I *think* this is a bug in Moose 0.87, but I'm unsure.

Nope, this is a bug in Moose 0.01 (or whatever version it was we introduced roles in).

<snip>

I would suggest a couple of things.

1. A class's attributes should silently override a role's attributes (I hate this, but it's in keeping with what Moose does with class/role methods).

The problem with this is that many of the roles methods will likely expect the attribute (and it's associated methods) to be there, so silently overriding a role attribute is pretty much totally out of the question. While kinda annoying we do the only sensible thing here, which is to die.

2. A class's methods MUST fail when trying to override a role's attributes, thus forcing deliberate exclusion. This is because methods and attributes serve such radically different purposes that a failure here is warranted.

Yes, I will agree here, but as of right now role attributes are not first class citizens so this is not possible. There was a lot of talk at this years YAPC::NA hackathon about actually making them first class, so rest assured it is being addressed.

3. A class's attributes MUST fail when trying to override a role's methods (see above).

Same as #2, I agree with you, but until we have first class role attributes and can calculate the methods they will create at composition time, we won't be able to fix this.

I expect that people will have different views, but that's OK. Have fun with that.

Well not me, except for the first item, which I suspect if you read my point there you will agree with me on.

I would also love to see private attributes available for Moose roles.

Yuk! I would prefer to solve the problem without them if possible. Privacy in perl is hackish at best since you cannot really properly hide a method from the dispatcher (just die-ing when called from an improper context is *NOT* correct privacy, true privacy means that the method is invisible to the dispatcher and not present in the MRO).

Right now, that attribute in the role unnecessarily clutters the interface of the class and there's no need for that.

Unless of course that is the purpose of the role, to include an attribute. Sure I can see occasions when you do want to hide data, but it is not always the case and I would argue is more an exception then it is a rule.

Plus, the class shouldn't have to provide an attribute which only that role uses because that violates the role's encapsulation.

I think that the roles encapsulation has already been violated when it was completed taken apart and all it's component bits shoved (composed) in the class. Again, there is a case for data-hiding in some cases, but certainly not all.

So, now that is out of the way, let me explain the rough plan that Yuval Kogman and I have been tossing around now for almost 3 years.

Class::MOP needs some kind of low level "trait"-like mechanism (in this context "trait" means the things in the original traits paper, which is just sets of pure methods and the composition rules that apply to them). These would be primarily an internally used feature too and not something exposed in the Moose sugar. Lets call these "mop- traits" for now to avoid any confusion.

Now, lets start with just classes first.

The idea is that an attribute meta-object can actually produce one of these "mop-traits" for itself. The result is a trait which defines all the methods that would be associated with this particular attribute. This would includee the reader/writer/accessor, predicate, clearer and delegated methods for handles, as well as required methods for the builder/lazy_build methods.

So then when a class is being composed, it asks each attribute (local and inherited) to supply a mop-trait. It then takes all these mop- traits and composes them all together into a single composite trait, applying all the conflict resolution rules you would expect. The result is that we would be able to tell at this point if any of our attributes conflict with one another.

Next we would ask the class itself to create a mop-trait of itself to represent all the methods (local and inherited) and we compose it with the attribute composite trait. We do this because we want to apply trait-style conflict detection and not class conflict detection (where the local class would win and superclass would loose, etc).

The end result is that we are assured that no attributes conflict with one another and no attribute created methods conflict with hand- defined methods.

Sounds nice doesn't it :)

It is also very expensive. Liberal use of caching and possibly doing the mop-trait composition with something more optimized like Set::Object would perhaps help, but this is all compile time cost and we already have a problem with that in Moose. And just wait, it gets worse, we haven't even gotten to roles yet.

So, now throwing roles into the mix.

A role attribute is a deferred attribute, meaning that it really has very little value (besides introspection maybe) until it is added into the class into which it is being composed. It also is context sensitive, different composition contexts can produce different results (parameterized roles, etc).

We would need to break down all the roles into sets of mop-traits in the same was a the classes (first each of the attributes, then the methods). This isn't as bad for roles since they are already flattened and have no indirection to resolve. This would assure us that each single role itself would be internally consistent.

We would then need to compose all these roles together (assuming your composing multiple roles into your class) so that we could be sure that this particular combination of roles is valid and non-conflicting.

Then we would need to take all the mop-traits of our roles and compose them with all the mop-traits of our class (it's attributes and methods, as discussed above), and let me tell you, that there is a whole mess-a composin' going on.

A conflict in this process would basically signal a composition problem, and if we add some more error context info to the required methods that are produced by these conflicts we should be able to give some pretty good error messages, especially since we wont have to die at the first sign of error.

Now, again, we certainly will be able to cache things, especially if we can keep mop-trait composition as a pure (side-effect free) computation.

...

So at this point when you call ->new on your class, it should shoot rainbows out of your computers USB drive and a unicorn should then appear and give you ice cream (or some kind of frozen tofu treat if your are lactose intolerant or a vegan).

---

Now, I will be honest, I highly doubt you will see this in Moose 1.0, to do it right would really require a major overhaul in how things work right now and would almost surely require us to be able to "compile" Moose classes (to .pmc files or something). Yuval explored a lot of these ideas (and a more compile-able meta-model) in his MO experiement, but that remains just a research project at this point. Yuval and I are still pondering this along with the rest of the Moose crew, so have faith.

- Stevan
































Reply via email to