At work, we've been experimenting with Moose because in our enormous and complex codebase we think we can probably benefit a lot from the extra rigour that it brings.
Before I continue, let me note that ours is a typical mod_perl enterprise setup with 6gig of memory per machine, so any memory consumed before the Apache fork is essentially free.
So none of the issues people (including me) have with startup code and memory consumption apply in this case, and I won't be addressing performance issues in this post.
The consensus of the half-dozen people at work is how Moose tries to look like a declarative extension, but doesn't actually act like it.
The following is what Moose seems to have been aiming for.
package Foo;
use Moose;
extends 'Bar'; with 'Role1'; with 'Role2';
has this => ( is => 'ro', isa => 'Str', );
has that => ( is => 'ro', isa => 'Str', );
1;
package Foo;
use Moose;
BEGIN { # When we use Catalyst extends 'Bar'; }
has this => ( is => 'ro', isa => 'Str', );
has that => ( is => 'ro', isa => 'Str', );
with 'Role1'; with 'Role2'; no Moose; __PACKAGE->meta->make_immutable;
package Foo;
use MooseX::Atom [ extends => 'Bar', with => 'Role1', with => 'Role2', has => [ this => ( is => 'ro', isa => 'Str', ), ], has => [ that => ( is => 'ro', isa => 'Str', ), ] ];
1;
package Foo;
use MooseX::Hash { extends => 'Bar', with => [ 'Role1', 'Role2' ], default => { is => 'ro' }, has => { this => { isa => 'Str' }, that => { isa => 'Str' }, }, );
1;
I wasn't aware that the extends needs to be in a BEGIN block unless you really do have code which needs to happen at BEGIN time. Am I wrong? (Could be).
Also, with accepts a list and using multiple with statements should be avoided unless you have a very, very good reason to do so. A single with statement is like this:
with qw(Does::Role1 DoesRole2);
Using it like that gets you the method conflict resolution. Using separate with statements is not only overly verbose, but it composes each role in separately. Since a role method is silently discarded if the class provides a method of the same name, if Does::Role1 provides a munge method and Does::Role2 also tries to provide it, you will never get Does::Role2's method composed in if you use with separately, but you will get a proper conflict when you use them at the same time.
Also, if you want to provide a default is for the has, make it 'ro' instead of 'rw'. It tends to be far more correct and makes it harder for people to accidentally alter the object's state in incompatible ways.
Re:Moose Notes
Alias on 2010-08-03T07:39:23
Eek, roles behaviour differently depending on whether you do them separately or together?
Yet another reason to be annoyed
:( Re:Moose Notes
Ovid on 2010-08-03T07:53:57
Eek, Perl's built-ins behave differently depending on whether or not you call them in scalar context? Yet another reason to be annoyed!
;) Seriously, it will take you about 3 seconds to get used to this behaviour of the with function and it's behaviour you want.
If you must write an alternative to the Moose API, I strongly recommend that you use Moose for a few months to get really comfortable with it and make sure you understand the design implications. Moose is great and well worth the learning curve. Just make sure that if you provide an alternate API that you understand why decisions have been made the way that they have. Breaking the rules is far more likely to be successful if you know why you're breaking them
:) Re:Moose Notes
Alias on 2010-08-03T08:01:26
It's not a case of getting used to it.
It's that it's a gotcha that the API should never have allowed.
And our team HAS been using Moose for several months. MooseX::Atom is the summary of all the bits of it we don't like.
Re:Moose Notes
Stevan on 2010-08-03T16:42:29
Eek, roles behaviour differently depending on whether you do them separately or together?
It's that it's a gotcha that the API should never have allowed.Actually there are very good reasons why there are two ways to compose roles.
The ideal way to compose roles is to do it all at once like Ovid showed. This takes better advantage of conflict checking because it first creates a composite role of all the roles passed to
with
and then applies that role to the class.The second way is to have multiple
with
statements. This way is not really recommended because it bypasses the conflict checking, which, oddly enough, is exactly why it is useful. As dagolden said, Moose expects you to know what you're doing.This is (IMO) very perlish as we suggest (in the docs) that you do it correctly, but we know that the real world is messy, so we allow you the option of the not-so-correct way if you need it.
b) Use this SECOND heavy sugar layer on top of the FIRST sugar layer, on top of Class::MOP.
Funny, doesn't MooseX::Atom qualify as exactly that?
Re:Moose Notes
Alias on 2010-08-03T23:38:22
No, this does NOT count as "heavy".
Perl 3,120 K
Perl + Moose 11,840 K
Perl + MooseX::Atom 11,852 KRe:Moose Notes
perigrin on 2010-08-03T15:49:53
I wasn't aware that the extends needs to be in a BEGIN block unless you really do have code which needs to happen at BEGIN time. Am I wrong? (Could be).
You're not wrong. Extending the parent class needs to happen at compile time in Catalyst because as dagolden mentions elsewhere Catalyst choose once upon a time to support
sub foo : Path(/) { }
syntax. The nature of that syntax and how Perl goes about making it "work" requires your class hierarchy to be resolved at compile time... hence the BEGIN.
I wonder if the newly released Devel::BeginLift is a way to solve the problem.
I like your MooseX::Atom, but I rather see the problems you mentioned solved without having to rewrite all of my Moose code.
Re:About the compile time thing
Alias on 2010-08-03T08:47:02
Me too
Re:MooseX::Declare
Alias on 2010-08-03T08:48:18
That would fall into the category of
"b) Use this SECOND heavy sugar layer on top of the FIRST sugar layer, on top of Class::MOP."
Re:MooseX::Declare
Alias on 2010-08-03T09:01:20
To further clarify based entirely on memory load overhead (using only require, so without any work done in import methods).
Perl 3.1 meg
Perl + Moose 11.8 meg
Perl + MooseX::Declare 21.6 meg
First, as a minor nit, you can declare multiple attributes at once, which is a little less repetitive.
has [qw( this that )] => ( is => 'ro', isa =>'Str' );
As for "with", do you understand in what cases you need to put "with" after you declare your attributes? Even if you do, does everyone on your team (now and in the future)?
The thing I've come to appreciate about Moose is that it expects you to know what you're doing. E.g. you compose roles after attributes when roles require those attributes.
Using sugar to put the roles "on top" (visually) with delayed composition makes it easier for people to not think about what they're doing. In many cases, laziness is great -- there are lots of things we'd rather not think about. But roles are not subclasses. They're not mix-ins. They have different semantics.
It seems to me like the designers of Moose have aimed to make the declarative syntax match the semantics pretty closely. Moose isn't trying to be sugary -- it's trying to be transparent. As each declaration is processed, the class evolves as per the declaration. That's a pretty good approach for keeping people out of trouble from lazy thinking about how Moose actually works.
To be clear, I do think the idea of a thin layer of sugar on top of Moose makes a lot of sense, particularly for things like autoclean and immutability, where you have more specific goals or constraints than Moose itself.
[As I understand it, the reason that Catalyst needs "extends" in a BEGIN block is because of the way that Catalyst uses Perl attributes on subroutines (which, despite the name, have nothing to do with Moose attributes). That's a Catalyst design choice, not a Moose one. Once the project committed to using subroutine attributes, they got all the warts that go with that.]
-- dagolden
Re:semantics vs syntax
Alias on 2010-08-04T01:11:13
The idea of encapsulation is the very reason that we have most APIs.
The idea that you don't NEED to know how something is implemented is always going to be better, as long as that lack of knowledge does not compromise the integrity of the systems or lead to perverted incentives.
What good is a car that requires an intimate understanding of the underlying engine and brakes and gearbox. It might be handy if you are an enthusiast of a racing car driver or pushing the limits in some other dimension.
What good is a toilet that requires you understand the sewerage system?
But for the masses, encapsulation and the hiding away of knowledge is the primary goal. Encapsulation creates specialisation, which creates productivity, which creates prosperity.
Re:semantics vs syntax
dagolden on 2010-08-04T01:36:50
It sounds like you're talking about encapsulation from the standpoint of users and APIs, which is different. Or, rather, it's the same but applied to Moose itself rather than what someone is designing using Moose.
Unlike users, the designers of a system must understand the dynamics of the design. That can't be safely abstracted away. Role composition is a design activity that involves satisfying certain constraints and avoiding certain conflicts in order to get the desired behaviors.
My point was that the Moose API is very transparent about these design decisions and the temporal order in which they are carried out. The Moose API could have provided visual sugar which obscured the temporal order, but it did not. I think that's the right design decision for a very complex tool like Moose.
MooseX::Atom provides an alternate approach that appears to optimizes visual clarity over design clarity. That's a valid choice if you prioritize the other way.
-- dagolden
Re:semantics vs syntax
Aristotle on 2010-08-11T05:17:05
The composition order of roles is not an implementation detail of Moose; it’s a design concern of the creator of the class into which the roles get composed. To ask for it to be abstracted away is like calling maths is badly designed because
3 + 5 * 2
yields different results based on the order in which you evaluate the terms.
Theoretically, Moose could use Hooks::EndOfScope like namespace::clean, but having a dividing line in the middle of a module can be useful.
If someone would get his act together, we'd have a non-dev release of Perl::Critic::Moose out that can check a lot of this stuff for you, e.g. that you don't use with() multiple times.