http://www.perlmonks.org?node_id=695034

dmorgo has asked for the wisdom of the Perl Monks concerning the following question:

I'm still fairly new to objects in Perl, having tried several of the approaches recommended in different places (perlboot, perltoot, PBP). Now I'm taking a stab at using Moose for my next project.

I'm getting very frustrated with the Moose documentation. There is certainly a ton of documentation, but it skips from bare basics, up to the clouds, with very little in between. It has a nice Point example, which is simple and great, but then it jumps directly from that into highly arcane examples that have me surfing around on perlpatternswiki reading about Lisp, Haskell, and different theories of programming languages, where I can't tell if the parts talking about Perl were written before, or after, the appearance of Moose.

For Dog's sake, all I want to do is add and remove items to and from an array, where the array is a member variable. Or it can be an arrayref; fine.

But rather than complain any more than I already have, I coded up a simple example of what I'm trying to do. I hope monks wiser than me can point out a better way.

Here is my code. I'd suggest skipping down the the question at the very end first, so you know what part of the code to look at. The first file is lib/MyHolder.pm:

{ use strict; package MyHolder; use Moose; has 'name' => (is => 'rw', isa => 'Str'); has 'things' => (is => 'rw', isa => 'ArrayRef'); } 1;

The second file is t/test_holder.t, which exercises the MyHolder object a bit.

#!/usr/bin/perl use lib 'lib'; use strict; use warnings; use MyHolder; use Test::More tests => 7; BEGIN { use_ok( 'MyHolder' ); } require_ok( 'MyHolder' ) or exit; my @temp = (); my $cup_holder = MyHolder->new(); # name $cup_holder->name("cupboard"); is ($cup_holder->name(), "cupboard", "name of cup holder should be cupboard"); # things my @cups = qw(blue green green yellow); $cup_holder->things(\@cups); @temp = @{$cup_holder->things()}; is (scalar @temp, 4, "there should be four things in the cupboard"); # add an element to the array... you've got to be kidding me # push($cup_holder->things(), "red"); @temp = @{$cup_holder->things()}; push(@temp, "red"); $cup_holder->things(\@temp); @temp = @{$cup_holder->things()}; is (scalar @temp, 5, "there should be five things in the cupboard"); # remove the last element from the array... (!) # my $thing = pop $cup_holder->things(); @temp = @{$cup_holder->things()}; my $thing = pop @temp; $cup_holder->things(\@temp); @temp = @{$cup_holder->things()}; is (scalar @temp, 4, "there should be four things in the cupboard"); is ($thing, "red", "the cup should be red");

Allow me to direct your valuable attention to the last two sections, headed by comments 'add an element' and 'remove an element'. The commented-out lines with simple push() and pop() show what I would *like* to do; the ones below that are what I seem to have to do. The code runs fine; it just seems ugly to have to use a temp array here. Is this really the best way? Please say no.

P.S. There are some MooseX modules like MooseX::AtributeHelpers that offer more options (and lots more arcane programming theory buzzwords, too, not that that's a bad thing). And I definitely will explore those modules. But at the same time I'm still wondering what are the good (simple, short, elegant) ways to work with arrays just with Moose and without extension or experimental modules.

Replies are listed 'Best First'.
Re: Using ArrayRef data members in Moose
by Fletch (Bishop) on Jul 01, 2008 at 21:55 UTC

    The ArrayRef accessor returns an arrayref which you can use like any other arrayref . . .

    package Spoo; use Moose; has 'a' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] }, ); no Moose; package main; my $s = Spoo->new; push @{ $s->a }, qw( a b c ); my $idx = 0; for my $x ( @{ $s->a } ) { print $idx++, ": $x\n"; } exit 0; __END__

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

Re: Using ArrayRef data members in Moose
by dragonchild (Archbishop) on Jul 01, 2008 at 23:44 UTC
    Take a look at http://search.cpan.org/src/SARTAK/MooseX-AttributeHelpers-0.12/t/002_basic_array.t - that's the test that exercises the AttributeHelper that provides an array. You kinda have to look at it sideways a bit, but everything's there. The rosetta stone might be at the bottom.

    This does bring up an interesting question - how much of this do you really want Moose to do? This isn't idle speculation, either. I wrote some of the initial pieces of Moose with stvn and I think it's a phenomenal piece of work and should be used by most people in most places. Except, I've never used it in either work, OSS, or play. Part of that is I am completely comfortable with Perl's OO systems, having written about 4 myself and debugged several others. Part of that is that most of my work predates when Moose was actually usable. Part of that, though, is that I just don't drink that much koolaid. At some point, the syntactic sugar just gets to be too much and it's just simpler to go to another language like Ruby, Python, Javascript, or Smalltalk. Even stvn would agree that some of the really esoteric bits of Moose aren't ever going to be usable. Sometimes, a blessed hashref with cut'n'paste mutators is the right way to go.

    sub foo { my $self = shift; $self->{foo} = shift if @_; return $self->{foo}; }
    I'm not arguing against frameworks. I love them. I'm just saying that sometimes, a framework is inappropriate and you should learn to tell when that is.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      dragonchild, what you wrote really resonates with me. Moose looks incredibly shiny. But when I need to get something done, I find myself throwing something together with Class::Struct or "classic" blessed hash-refs.

      If I were to start a new complex, large project, I would seriously consider Moose (and build some test code before committing to it, you can be sure). But for most smallish projects, a hashref is plenty good enough.


      TGI says moo

      At some point, the syntactic sugar just gets to be too much and it's just simpler to go to another language like Ruby, Python, Javascript, or Smalltalk

      And give up CPAN? Silly hacker, no cookies for you! :P

      We would all love to live in a perfect polyglot world (especially a programming language fetishist like myself), but it just simply does not exist in reality. The level of sophistication of Perl libraries and the amount of battle testing the best ones have endured is the whole reason that I wrote Moose (surely you remember, you were there :P).

      Even stvn would agree that some of the really esoteric bits of Moose aren't ever going to be usable.

      Nope, I would most definitely NOT agree with you on that, not in the least :)

      The deep esoteric bits are not there for the casual user, they are their for the deep meta-hacker, or the MooseX:: author. Moose is layered as such that you only need to use as much as you want too, there are many rest-stops on the way down the rabbit hole.

      Sometimes, a blessed hashref with cut'n'paste mutators is the right way to go.

      Sure if you get paid by the LOC or better yet get paid by the hour so you can make the big bucks tracking down typo-bugs. Implementing (and re-implementing and re-implementing and re-implementing) the mechanisms of OOP for every single class is just plain wasteful, as wasteful as it would have been for me to have re-typed "and re-implementing" 3 times above instead of using copy/paste.

      -stvn
      Sometimes, a blessed hashref with cut'n'paste mutators is the right way to go.

      That's when I use Coat;

      Sure Moose tries to only pull in the parts you need, but it does rely on a fair few dependencies for what I want 95% of the time, just a simple class builder.

      Here's hoping Moose makes it into 5.12 Core (but not likely!)

        That's when I use Coat;

        Actually the recommended mini-Moose is now Mouse, it was written by and maintained by one of the core Moose contributors and unlike Coat it is commited to stay 100% compat with Moose (it even has a module Squirrel which will choose Moose or Mouse depending on if Moose is already loaded or not). This is of course not to say that Coat is a bad choice, only that the author has decided compat with Moose is not as important to him, so if that is important to you, then use Mouse.

        Here's hoping Moose makes it into 5.12 Core (but not likely!)

        Here is hoping it doesn't!!

        When a module goes into the core it suddenly has a dual-life. There is the (frozen) version that is distributed with the latest version of perl, and then the (living) version that is on CPAN (you now, the one with the bugs fixed and new features). Not until Moose is completely bug free and feature complete will I allow it to get put into core, in other words, it is never going to happen :)

        -stvn
Re: Using ArrayRef data members in Moose
by friedo (Prior) on Jul 01, 2008 at 21:55 UTC

    This isn't really a Moose issue, it's a Perl one. push and pop must take an array, not an array reference. So you'll need to dereference the arrayref to use those functions on it. You could easily add a method to wrap push and pop, but that seems kinda lame.

    There's MooseX::AttributeHelpers::Collection::Array, which certainly sounds like it would be useful, but it beats the hell outta me what it does. Unfortunately I'm quite the Moose n00b myself.

      True.

      By way of contrast, I figured out the sugar for using HashRefs. Hit on this by "genetic programming" -- trying different things until something worked:

      $obj->count_by_word()->{$word} = $count;
      It would be great if there was something similarly simple for arrayref access -- I'm suspecting that there is, and I just haven't found it.
        I think that it works quite the same with arrayrefs:
        $obj->weight()->[0] = 10;
        By way of contrast, I figured out the sugar for using HashRefs.

        This is not sugar at all, this is just how HASH refs work. The "count_by_word" accessor is simply returning the HASH ref value that is stored, nothing more nothing less.

        -stvn
Re: Using ArrayRef data members in Moose
by Arunbear (Prior) on Jul 02, 2008 at 09:48 UTC
    How about this:
    #!/usr/bin/perl use lib 'lib'; use strict; use warnings; use MyHolder; use Moose::Autobox; use Test::More tests => 6; BEGIN { use_ok( 'MyHolder' ); } my $cup_holder = MyHolder->new(); # name $cup_holder->name("cupboard"); is ($cup_holder->name(), "cupboard", "name of cup holder should be cupboard"); # things my @cups = qw(blue green green yellow); $cup_holder->things(\@cups); is ($cup_holder->things->length, 4, "there should be four things in th +e cupboard"); # add an element to the array $cup_holder->things->push("red"); is ($cup_holder->things->length, 5, "there should be five things in th +e cupboard"); # remove the last element from the array my $thing = $cup_holder->things->pop; is ($cup_holder->things->length, 4, "there should be four things in th +e cupboard"); is ($thing, "red", "the cup should be red");
    which gives
    $ prove -v t/test_holder...... 1..6 ok 1 - use MyHolder; ok 2 - name of cup holder should be cupboard ok 3 - there should be four things in the cupboard ok 4 - there should be five things in the cupboard ok 5 - there should be four things in the cupboard ok 6 - the cup should be red ok All tests successful. Files=1, Tests=6, 3 wallclock secs ( 0.01 usr 0.00 sys + 2.52 cusr + 0.03 csys = 2.56 CPU) Result: PASS
    Or you could make 'things' have type Object::Array instead of 'ArrayRef', for similar results but without autoboxing.
      Cool! This is very appreciated. I'll give it a whirl, and read up on autoboxing to grok what that means. And will try the other approaches too. Thanks for taking the time to answer.
Re: Using ArrayRef data members in Moose
by stvn (Monsignor) on Jul 03, 2008 at 02:59 UTC

    There is nothing magical about an attribute that isa => ArrayRef any more than an attribute that isa => Str. The former holds an array reference, the later a string. That is all, no other magic is done under the covers to make Perl 5's ugly dereferencing syntax go away.

    Simply put, I think you are expecting to see magic where none actually exists.

    I'm getting very frustrated with the Moose documentation. There is certainly a ton of documentation, but it skips from bare basics, up to the clouds, with very little in between

    Yes, Moose documentation is lacking in places, we know this and are working to improve it, help is always appreciated. I run an audreyt/Pugs-style project too, so if you want a commit-bit just come on over to #moose and ask for one and you can start improving the docs right away.

    In the meantime here are some suggestions which you may not have seen, all of which are accessible from the Moose homepage http://moose.perl.org:

    -stvn
      OK, thanks. That makes it much more clear (no magic). I was indeed (wrongly) expecting some, but it makes sense now that it's not there. And I will try out the MooseX::AttributeHelpers module(s) someone with a similar first name to yours (and others) suggested.

      As far as docs being lacking, what concerned me was that what I saw in the docs was that yes, you are aware that they are still in progress, but the awareness is centered on awareness that all the super-duper advanced usage is not yet documented. I didn't see any hint of awareness that the medium-level stuff is not documented. Maybe because what is obvious to you does not seem to need documenting, because it's so obvious. This feedback is meant to be constructive, and I appreciate the invitation to participate. Will decide later whether to take you up on it, after seeing how much more understanding I achieve.

      In the meantime I'm very excited to start making better use of Moose through some of the ways that have been pointed out here. Thanks for all you have put into it.

Re: Using ArrayRef data members in Moose
by stephen (Priest) on Jul 04, 2008 at 00:25 UTC
    I understand that you want to avoid using extension modules, but in this case AttributeHelpers does almost exactly what you want.
    package Holder; use Moose; use MooseX::AttributeHelpers; has 'things' => ( metaclass => 'Collection::Array', is => 'rw', isa => 'ArrayRef[Str]', default => sub { [qw/blue green green yellow/] }, auto_deref => 1, provides => { 'push' => 'add_things' }, ); package main; my $holder = Holder->new(); print "My things are:\t\t", join(' ', $holder->things() ), "\n"; $holder->add_things('mango'); print "My things are now:\t", join(' ', $holder->things() ), "\n";
    And the output:
    My things are: blue green green yellow My things are now: blue green green yellow mango
    Moose doesn't offer lvalue accessors, which I believe is a deliberate choice.

    stephen

      Moose doesn't offer lvalue accessors, which I believe is a deliberate choice.

      Yes, very deliberate. Lvalue accessors are broken by design for anything other then simple accessors which require no validation or processing of arguments at all. The only way to get around this is to use tie() and to put all the validation/arg-processing in the STORE method that tie() uses. However this is a completely incorrect division of responsibility not to mention slow and kludgey, and all for such a small amount of syntactic sugar, it just wasn't worth it.

      -stvn
      I was looking for this kind of behavior in a module I'm writing, and found this thread very useful. I'd like to point out that the functionality provided by MooseX::AttributeHelpers has been added to core Moose, and it's very slick. Read about it on CPAN: http://search.cpan.org/~drolsky/Moose-1.14/lib/Moose/Meta/Attribute/Native.pm
Re: Using ArrayRef data members in Moose
by sundialsvc4 (Abbot) on Jan 23, 2009 at 17:42 UTC

    I want to reopen this thread about Moose::AttributeHelpers::Collection::Array because I just don't get it!

    Will someone who speaks in plain language please explain to me exactly what this does? Exactly what it is saying to Perl or to Moose?

    provides => { 'push' => 'add_things' },

    When I look at this code (and elsewhere...) I do not see the word “push!” I see people using a method, “add_things,” plenty of times, but that looks like an ordinary method-call ... but what sort of stale-tasting “sugar” is this?

    My hand is once-again poised in front of my forehead, ready to slap... (Whack! “Doh!!”) :-)

      That's saying that the class' instances have a method add_things which when called will push its arguments on to the end of the arrayref instance variable in question. It's sugar for something like this:

      package Foo; use Moose; has 'some_method' => ( is => 'ro', isa => 'ArrayRef', default => sub { +[] }, ); sub add_things { my $self = shift; push @{ $self->some_member }, @_; }

      The provides makes it write methods which perform that operation (be it push, or pop, or keys, or . . .) on the instance's member in question.

      Update: Minor wording tweaks and removed metaclass declaration from sample code since it's superfluous.

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

      Well, whaddaya know...

      Whack! “Doh!!”

      I figured it out!

      It seems that the magic is this:   this definition adds new methods, to the object, not the array attribute, which have the “magical effect” of doing the appropriate operation to the appropriate attribute... automagically knowing both what the attribute is, and what to do with it.

      What was throwing me off, for the longest time, was this incorrect code:

       $n = $object->items->num_items();  ##INCORRECT
      which was giving me the very mysterious error:
      # died: Can't call method "num_items" on unblessed reference...

      Which is incorrect because the method is attached to the object. Oh... I get it... Gee, it's even seriously cool...

      Therefore, this works:

       $n = $object->num_items();  ##CORRECT

        I know this is OLD! But i want to post this example, that works
        use MooseX::Declare; ... ... ... has 'error_log' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } ); ... ... ... method register_error { shift; my $s_message = shift; return unless defined $s_message; my @a_temp = ( $s_message ); $self->error_log->push ($s_message); return $self->error_log->length; }
        self->error_log->push does not work until i assign a "default" for the array.
        I know this is OLD! But i want to post this example, that works
        use MooseX::Declare; ... ... ... has 'error_log' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] } ); ... ... ... method register_error { shift; my $s_message = shift; return unless defined $s_message; $self->error_log->push ($s_message); return $self->error_log->length; }
        self->error_log->push does not work until i assign a "default" for the array.