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)

Peter V. Mørch
  • 13,830
  • 8
  • 69
  • 103

1 Answers1

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

AKHolland
  • 4,435
  • 23
  • 35
  • 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?](http://stackoverflow.com/a/28140469/345716). It does get quite ugly if I need to initialize _options, though. – Peter V. Mørch Jan 25 '15 at 19:25
  • So now there's a need for Moose generics / templates. Because now I can see myself creating 99.5% boilerplate implementations of `Array_of_` and `Hash_of_` 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?) – Peter V. Mørch Jan 25 '15 at 19:33
  • 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 Apr 26 '18 at 18:17
  • @UncleCarl what version of Moose do you have? This convention is still recommended in the current documentation http://search.cpan.org/dist/Moose/lib/Moose/Manual/Delegation.pod – AKHolland Apr 30 '18 at 19:52
  • @AKHolland, Moose version 2.2004. Thanks. – UncleCarl May 01 '18 at 21:20
  • 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. – AKHolland May 04 '18 at 16:15