Who's going to make Moose class declarations simpler?

A Stackoverflow answer that encourages the questioner to use Moose has a long code example because it has a lot of code and formatting since there is a lot of repeated typing:

has 'PacketName' => (
    is       => 'rw',
    isa      => 'Str',
    required => 1,
);

has 'Platform' => (
    is       => 'rw',
    isa      => 'Str',
    required => 0,
);

has 'Version' => (
    is       => 'r',
    isa      => 'Int',
    required => 1,
);

I'd like to see that look something like little language this instead, perhaps in a INI style format. Assume that the attributes are rw and required (or whatever the defaults should be) and only express the deviations:

 # file is fooclass.moose or something
  [has]
  PacketName
  Platform         not_required
  Version          isa Int is r

Ultimately, I'd like to see a non-code text description of a class come from configuration rather than code. I've talked to random Moose people around this and they've never shot it down (to my face :). I've done this for non-Moose things, some of which I talk about in Mastering Perl, but that you can also see in some of my work on Data::Constraint and Brick.

I know there are a lot more fancy things that you can do and you might use code to figure some of that out, so you wouldn't use this in those cases. However, in the newbie, simple class case, it's a lot less scary to see a little code than a lot of code.

It's not very high on my to do list, though, so steal the idea if you like it.

10 Comments

Moose::Manual::Attributes says you can do this:

has [ 'x', 'y' ] => ( is => 'ro', isa => 'Int' );

-- dagolden

You could probably do something like:

has ['x','y'] => qw(is ro isa int);

I'm not sure that's better. Personally I don't find the attribute declaration so verbose. You can often leave stuff off, for example

has [qw(a b c e d)] => (is=>'ro');

I do similar to this a lot when I don't have strong needs for validation off the top.

When Moose exports helpers like 'has', 'when' and 'extends', it's (even temporarily) polluting your namespace. Extending even more would pollute it even more and potentially cause issues. If that syntax could be declared in a clean lexical scope which isn't too off-putting, I'd like to see it.

Perhaps one route would be to use UPPER CASE? I wrote code that allowed one to write storage-agnostic searches in Perl like this:

    AND(
        name      => 'foo',
        last_name => 'something',
    ),
    OR( age => BETWEEN [ 13, 21 ] ),
    OR(
        fav_number => GE 42,
        some_value => 'bob',
    )

It worked pretty well. Perhaps a similar scheme?

    has ['x','y'] => IS RO, ISA INT;

That would still be valid Perl and allow Devel::Cover to handle it nicely.

Why not make 'has' take a block instead of a hash?


has { name 'x'; is 'ro'; isa 'Int'; required;}

Make required() with no arguments be the same as required() with a true argument; you have to explicitly pass a false argument to make an attribute not required.

Have name() set up a structure that is, isa, etc use, and they apply their arguments to each name in the list of arguments provided to name().

The problem I see is in keeping name(), is(), etc from polluting your namespace. If they could be exported only to the namespace of the anonymous sub, that would solve the problem....

I was going to jokingly point you to XML::Toolkit which will take an XML document like:

<MyApp>
    <Packet/>
    <Platform/>
    <Version />
</MyApp>

and turn it into a Class like:

package MyApp::MyApp;
use Moose;
use namespace::autoclean;
use XML::Toolkit;

has 'Packet_collection' => (
isa => 'ArrayRef[MyApp::Packet]',
is => 'ro', init_arg => 'Packets',
traits => [qw(XML Array)],
lazy => 1,
auto_deref => 1,
default => sub { [] },
handles => { add_Packet => ['push'] }, description => {
Prefix => "",
LocalName => "Packet",
node_type => "child",
Name => "Packet",
NamespaceURI => "",
sort_order => 0,
},
);
has 'Platform_collection' => (
isa => 'ArrayRef[MyApp::Platform]',
is => 'ro', init_arg => 'Platforms',
traits => [qw(XML Array)],
lazy => 1,
auto_deref => 1,
default => sub { [] },
handles => { add_Platform => ['push'] }, description => {
Prefix => "",
LocalName => "Platform",
node_type => "child",
Name => "Platform",
NamespaceURI => "",
sort_order => 1,
},
);
has 'Version_collection' => (
isa => 'ArrayRef[MyApp::Version]',
is => 'ro', init_arg => 'Versions',
traits => [qw(XML Array)],
lazy => 1,
auto_deref => 1,
default => sub { [] },
handles => { add_Version => ['push'] }, description => {
Prefix => "",
LocalName => "Version",
node_type => "child",
Name => "Version",
NamespaceURI => "",
sort_order => 2,
},
);
1;

__END__


But somehow I don't think that's what you want. I don't see anything *wrong* with auto-generating Moose classes (I infact have written at least two modules that do exactly that!), I just don't have a generic need for it either.

Finally, I have no clue how to make the code in comments "pretty". If this looks horrible, my apologies.

I can't understand what you're trying to solve here. If it's just that you think Moose is verbose and yucky just wait until you try to do something trivial with your `.ini` file. `has()` doesn't just take key-value pairs as I'm sure you're aware of, as soon as you add a `default` or `trigger` there things are going to get real nasty really fast.

If you want machine readable attributes wouldn't it be better to put them all in a Moose::Role and read them with PPI?

Leave a comment

About brian d foy

user-pic I'm the author of Mastering Perl, and the co-author of Learning Perl (6th Edition), Intermediate Perl, Programming Perl (4th Edition) and Effective Perl Programming (2nd Edition).