Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

How do I work with multidimensional arrays in Moose?

by paulymer (Novice)
on Sep 03, 2013 at 12:21 UTC ( #1052095=perlquestion: print w/ replies, xml ) Need Help??
paulymer has asked for the wisdom of the Perl Monks concerning the following question:

Dear all,

I would like to use a 2d multidimensional array in Moose, but have a few questions. What is the proper way to build a two-dimensional array? In particular, I would like an array-of-arrays-of-objects which has a type 'ArrayRef[ArrayRef[Object]]'. Manipulating the outer dimension is easy enough through the native 'Array' methods such as push, pop, exists, etc. But the inner dimension is a little more tricky and I haven't found any information in my searches how to properly handle the inner arrays of objects. Here is what I have found:

#!/opt/local/bin/perl -w { package Obj; use Moose; has 'name' => ( is => 'rw', isa => 'Str', default => '', ); sub print { my $self = shift; print $self->name; } no Moose; __PACKAGE__->meta->make_immutable; } { package AoAoObj; use Moose; use Moose::Util::TypeConstraints; has 'arr2d' => ( traits => ['Array'], is => 'ro', isa => 'ArrayRef[ArrayRef[Obj]]', required => 1, default => sub { [] }, handles => { _push => 'push', }, ); sub addAoObj { my ($self, @obj) = @_; $self->_push([ @obj ]); } sub addInnerObj { my ($self, $index, @obj) = @_; #assert validity of objects pushed #onto inner arrays-of-objects my $constrAoAoObj = find_type_constraint('Obj'); for my $o (@obj) { $constrAoAoObj->assert_valid($o); } push @{ $self->arr2d->[$index] }, @obj; } sub print { my $self = shift; my $aref = $self->arr2d; for my $x (@$aref) { for my $y (@$x) { $y->print; print "\t"; } print "\n"; } } no Moose; no Moose::Util::TypeConstraints; __PACKAGE__->meta->make_immutable; } use strict; my $struct1 = AoAoObj->new; for my $x (0 .. 4) { for my $y ("A" .. "E") { my $myobj = Obj->new( name => "$y$x" ); $struct1->addInnerObj($x, $myobj); } } $struct1->print; $struct1->addInnerObj(3, 'Whammi'); # succeeds unless data validation +code is added to the method $struct1->print; print "END of struct1\n\n"; my $struct2 = AoAoObj->new; my $o1 = Obj->new( name => 'X1' ); my $o2 = Obj->new( name => 'Y1' ); my $o3 = Obj->new( name => 'Z1' ); $struct2->addAoObj($o1, $o2, $o3); # success; pushes [X1 Y1 Z1] $struct2->print; $struct2->addAoObj( qw/Whammi1 Whammi2/ ); # fails validation $struct2->print; print "END of struct2\n\n";

There is a lot going on here, so I will try to explain as simply as I can. I have two methods for pushing data on the 'arr2d' attribute. First, I have 'addAofObj'. This takes an array-of-objects and pushes it onto the outer array. Data validation works perfectly in this case, without any extra work. If I try to push an array-of-strings, for instance, the script fails as expected. This is illustrated at the bottom of the script with the $struct2 operations. The downside to this method is that I cannot extend rows; I can add more pre-formed rows, but they can't be extended.

The second method I use for pushing data is intended to overcome the limitation of the first method shown above. This method is 'addInnerObj'. In this case, I can extend any given row, but now there is no type validation unless I code it into the method. If I comment out the type validation steps, and just push the data provided by the caller:

sub addInnerObj { my ($self, $index, @obj) = @_; #assert validity of objects pushed #onto inner arrays-of-objects #my $constrAoAoObj = find_type_constraint('Obj'); #for my $o (@obj) { #$constrAoAoObj->assert_valid($o); #} push @{ $self->arr2d->[$index] }, @obj; }

no warnings or errors are triggered until further downstream when methods which don't exist are called on the elements of the array. This is illustrated with the $struct1 operations. In this case, if the caller provides a 'Str' instead of an 'Obj' type, the string is pushed onto the end of the row with no complaint from the class. It appears that using the builtin 'push' circumvents the Moose type validation infrastructure. I can correct for this by adding my own data validation, but I fear that this will come back to bite me if I subclass, or do other complex operations. So, back to my original question: what is the proper way to work with multidimensional arrays/structures with Moose? Thank you in advance for your help.

Comment on How do I work with multidimensional arrays in Moose?
Select or Download Code
Re: How do I work with multidimensional arrays in Moose?
by glasswalk3r (Pilgrim) on Sep 03, 2013 at 14:38 UTC

    First, I would recommend using <readmore> tags around your code section, long posts make it difficult to get help.

    Regarding your question, I believe Moose does not have anything related to your task at hand but I may be wrong. Did you try searching CPAN first?

    Anyway, you should use "regular" code to deal with your array references. Moose will help you only with the type checking that you used when creating the attribute. Create methods to deal with the array references, and just be sure to assign the proper value when writing back to the attribute.

    Anyway, since you're dealing with references, you could change the data inside it without accessing the attribute anyway, but this might be dangerous if done outside the class methods.

    Looks like that you're expecting Moose to validate the data, but to have that you would need to invoke the set or whatever you have defined as writer for the arr2d attribute.

    If you're not trying to improve performance by using array references, I would suggest you to create classes to represent the data structure that you want instead of plain data. That would help using an easier way to change data inside the structure and being able to check for problems during the process.

    I would also suggest you to choose better names for the attribute and methods, but maybe you're justing experimenting.

    Alceu Rodrigues de Freitas Junior
    ---------------------------------
    "You have enemies? Good. That means you've stood up for something, sometime in your life." - Sir Winston Churchill

      Thank you for the suggestions, glasswalk3r. I have added the <readmore> tags; much better, I agree. Yes, I was expecting Moose to validate the data, but obviously I've not constructed the class correctly for Moose to do that. An example of how to do this would be very helpful.

Re: How do I work with multidimensional arrays in Moose?
by tobyink (Abbot) on Sep 03, 2013 at 15:00 UTC

    You could take a look at List::Objects::WithUtils and List::Objects::Types.

    (I might pop back later and add an example of using them.)

    Update: OK, I'm back! The TypedArray type constraint from List::Objects::Types didn't have quite the features I needed for the example, so I submitted a patch to the module's author, and he's released a new version.

    Here's my example now...

    Here's an alternative built using my Moops OO framework which I'm currently in pimping mode for. Its load time is about 35% faster than the Moose version above.

    There is some scope for improvement. For example, the code assumes that each row will hold the same number of cells. (That is, the grid doesn't have a "ragged right edge".) It might be a good idea to assert this in the code some places, to make sure that it's always the case.

    use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

      Thank you, tobyink. I looked at the links, but it appears that they mostly deal with 1D structures (unless I missed something). How would I use these for 2D structures? And yes, an example or two would be much appreciated.

      Further to that, here's the Moose example reworked to use MooseX::Role::Parameterized; this factors out the grid-related logic into a role, to make it easier to create classes that are a grid of Cell objects, or a grid of strings, or a grid of filehandles, or whatever...

      I really need to add some sugar for parameterized roles to Moops. :-)

      use Moops; class Cow :rw { has name => (default => 'Ermintrude') }; say Cow->new->name

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1052095]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (6)
As of 2014-07-25 04:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    My favorite superfluous repetitious redundant duplicative phrase is:









    Results (167 votes), past polls