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