5

I'm beginning to realize that this is for beginners:

package Bad;

has 'arr' => ( is => 'rw', 'ArrayRef[Str]' );

package main;

my $bad = Bad->new(arr => [ "foo", "bar" ]);
print $bad->arr->[0], "\n";

Enter traits. I'm underwhelmed by the traits API, though. Have I misunderstood something? Can I get this API instead somehow? :

print $bad->arr->get(0), "\n";

Details

Review the canonical traits example from Moose::Meta::Attribute::Native::Trait::Array

package Stuff;
use Moose;

has 'options' => (
    traits  => ['Array'],
    is      => 'ro',
    isa     => 'ArrayRef[Str]',
    default => sub { [] },
    handles => {
        all_options    => 'elements',
        add_option     => 'push',
        map_options    => 'map',
        filter_options => 'grep',
        find_option    => 'first',
        get_option     => 'get',
        join_options   => 'join',
        count_options  => 'count',
        has_options    => 'count',
        has_no_options => 'is_empty',
        sorted_options => 'sort',
    },
);

no Moose;
1;

An object declared like that is used e.g.:

my $option = $stuff->get_option(1);

I really don't like that for one array attribute I get and have to manually name 11 methods in my Stuff class - one for every single operation that one can do to 'options'. Inconsistent naming is bound to happen and it is bloaty.

How do I instead (elegantly) get an API like:

my $option = $stuff->options->get(1);

Where all the methods from Moose::Meta::Attribute::Native::Trait::Array are implemented in a type-safe way?

Then all the operations on every single Array are named in exactly the same way...

(I'm actually using Mouse, but most of Mouse is identical to Moose)

1
  • You could use autobox, but AKHolland's answer is much better. Commented Jan 28, 2015 at 3:45

1 Answer 1

5

I think that the best way to get your API into that format would be to create a new object for the options, and delegate the methods into it directly. Something like:

package Stuff;
use Moose;
use Stuff::Options;

has 'options' => (
    'is'      => "ro",
    'isa'     => "Stuff::Options",
    'default' => sub { Stuff::Options->new },
);

no Moose;
1;

And then in Stuff/Options.pm:

package Stuff::Options;
use Moose;

has '_options' => (
    'is'      => "ro",
    'isa'     => "ArrayRef[Str]",
    'traits'  => [ "Array" ],
    'default' => sub { [] },
    'handles' => [ qw(elements push map grep first get join count is_empty sort) ],
);

no Moose;
1;

This would allow code as in your example to work ($stuff->options->get(1)).

Sign up to request clarification or add additional context in comments.

5 Comments

This definitely answers my question. Thanks! In fact I think this is the only way I can create a hash-of-hashes which is why I've linked to this answer from another thread: How to store Hash of Hashes in Moose?. It does get quite ugly if I need to initialize _options, though.
So now there's a need for Moose generics / templates. Because now I can see myself creating 99.5% boilerplate implementations of Array_of_<class> and Hash_of_<class> for many of the built-in data types and my own classes. E.g. the above Stuff::Options is really just Array_of_Str. (I'd be totally fine with _options being called something generic like _array above). How do I get Array_of_Str created automatically without having to write the 13 lines of boilerplate? (Isn't this exactly the reason that templates in C++ and generics in Java were invented?)
I don't know if this has changed, but I get error "The 'handles' option must be a HASH reference, not ARRAY(0x9c92a9c)" when trying this.
@UncleCarl what version of Moose do you have? This convention is still recommended in the current documentation search.cpan.org/dist/Moose/lib/Moose/Manual/Delegation.pod
Seems to work fine for me in that version. Maybe you're doing something weird with roles? Would be a good question to post with more details.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.