Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Moose: How to get an array of objects? Traits?

Tags:

perl

moose

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)

like image 708
Peter V. Mørch Avatar asked Jan 22 '15 17:01

Peter V. Mørch


1 Answers

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)).

like image 157
AKHolland Avatar answered Oct 16 '22 23:10

AKHolland