Skip to content

Perl Moo attributes: hash and array

I am using modern Oriented Object approaches based on Moo – Minimalist Object Orientation (with Moose compatibility) to build a complex API framework integrated with cached systems, event queues, etc. Moo is a subset/fork/smaller/light version of Moose – A postmodern object system for Perl 5 – and is very fast, compared to Moose footprint.

Gabor Szabo, from PerlMaven.org, wrote a very cool tutorial on how to use Moo, and Ricardo Signes (Moose and many different modules developer) has an amazing 3-hour tutorial on how to use Moose. Both of them helped me a lot to understand and to migrate my old OO Perl code to this new approach.

To be honest, first time I used Moo, I found really awkward  how Hash or Array are created as Object’s attribute. Moo has no formal Type (Int, String, etc..), as Moose does. Thus, to create a hash or arrays as attributes, we need to establish a Anonymous Function to it, kind of lambda in Pyhon.

Update – 01/Nov/2017 start

First time I wrote this post I was thinking to use Hash and Array based on pure Moo modules. I had no idea about other CPAN modules. I was trying to write a code as minimal as I could, since I am kind of dumb to understand an APIs without examples :D. However, after I shared this post on Reddit /perl, I have been receiving so many great examples on how to use both data structures on Moo. Thank you all! I will incorporate them ASAP.

Update – 01/Nov/2017 end

Defining Arrays:

has os_array => (
 is => 'rw',
 default => sub { [] },
);

We have defined an attribute named as `os_array` that its `default` value is a anonymous function to a array reference. I assume that, if you have a better explanation, please let me know.

I found two ways out of billion (TMTOWTDI  –  ;p) to initiate this attribute. First is providing an array reference when Object is created:

package MooArray;

use Moo;
use feature 'say';
use Data::Dumper;

has os_array => (
 is => 'rw',
 default => sub { [] },
);

1;
no Moo;

#### Main code

my @array_ref = qw(Linux FreeBSD OpenBSD);

my $z = MooArray->new( 
 os_array => \@array_ref
);

say Dumper $z->os_array;

# Array operations
push @{$z->os_array}, 'Solaris';

foreach my $el ( @{$z->os_array} ) {
 say "Neo knows: ", $el;
}

say Dumper $z->os_array;

Which give us:

$VAR1 = [
 'Linux',
 'FreeBSD',
 'OpenBSD'
 ];

Neo knows: Linux
Neo knows: FreeBSD
Neo knows: OpenBSD
Neo knows: Solaris
$VAR1 = [
 'Linux',
 'FreeBSD',
 'OpenBSD',
 'Solaris'
 ];

Second approach is to access Object’s attribute directly:

package MooArray;

use Moo;
use feature 'say';
use Data::Dumper;

has os_array => (
 is => 'rw',
 default => sub { [] },
);

1;
no Moo;

#### Main code

my @array_ref = qw(Linux FreeBSD OpenBSD);

my $z = MooArray->new( );

say Dumper $z->os_array;

# Array operations
push @{$z->os_array}, 'Solaris';
foreach my $el ( @{$z->os_array} ) {
 say "Neo knows: ", $el;
}

say Dumper $z->os_array;

Providing us the following output:

$VAR1 = [];

Neo knows: Solaris
$VAR1 = [
 'Solaris'
 ];

Seems to be easy? Let’s see an example when we use array reference inside a function block:

package MooArray;

use Moo;
use feature 'say';
use Data::Dumper;

has os_array => (
 is => 'rw',
 default => sub { [] },
);

1;
no Moo;

#### Main code

my $z = MooArray->new( );
say Dumper $z->os_array;

sub my_scope {
 my @array_ref = qw(Linux FreeBSD OpenBSD);
 $z->os_array(\@array_ref);
}

my_scope();
say Dumper $z->os_array;

# Array operations
push @{$z->os_array}, 'Solaris';
foreach my $el ( @{$z->os_array} ) {
 say "Neo knows: ", $el;
}

say Dumper $z->os_array;

In both Moo or Moose, setters and getters are defined by default, which means that when we use $z->os_array(\@array_ref); we are using `os_array` setter  to set `array_ref` content.

Defining Hashes

When defining hashes, syntax gets bit difference:

package MooHash;

use Moo;
use feature 'say';
use Data::Dumper;

has os_hash => (
 is => 'rw',
 default => sub { {} },
);

sub set_me {
 my $self = shift;

my %age = (
 Linux => '1998',
 FreeBSD => '2010',
 );

$self->os_hash(\%age);
}

1;
no Moo;

#### Main code
my $z = MooHash->new();

$z->set_me();

say Dumper $z->os_hash;

#Hash operations
$z->os_hash->{Solaris} = 2004;
say Dumper $z->os_hash;

while ( my ($key, $value) = each %{$z->os_hash} ) {
 say "Running " . $key . " since: " . $value;
}

Showing the following output:

$VAR1 = {
 'FreeBSD' => '2010',
 'Linux' => '1998'
 };

$VAR1 = {
 'FreeBSD' => '2010',
 'Linux' => '1998',
 'Solaris' => 2004
 };

Running FreeBSD since: 2010
Running Linux since: 1998
Running Solaris since: 2004

When we use settters  $self->os_hash(\%age); to set attribute’s content, we have 3 different ways to declare hash attributes:

using {} operator:

has os_hash => (
 is => 'rw',
 default => sub { {} },
);

Using ( ) operator:

has os_hash => (
 is => 'rw',
 default => sub { () },
);

and

has os_hash => (
 is => 'rw',
 default => sub { },
);

In case we do not use setter, we can define an attribute:

package MooHash;

use Moo;
use feature 'say';
use Data::Dumper;

has os_hash => (
 is => 'rw',
 default => sub { {} },
);

1;
no Moo;

#### Main code
my $z = MooHash->new();

say Dumper $z->os_hash;

#Hash operations
$z->os_hash->{Solaris} = 2004;
say Dumper $z->os_hash;

while ( my ($key, $value) = each %{$z->os_hash} ) {
 say "Running " . $key . " since: " . $value;
}

Final words

If you need a stronger Type validation, please use Moose.

In Moo, since we are using an anonymous function (a pointer to something), any reference given to that attribute will behave as its reference. For instance, If my reference is an array, my attribute is array, however, when my reference is an hash, than I will have a hash attribute. I am not sure if this is a bug or not! (let me know).

Update – 01/Nov/2017 start

From a comment feedback

Grinnz

The defaults of `sub { () }` and `sub { }` are identical (there is no `()` operator) and just set the default to undef, since nothing is returned. Thus it’s autovivified to a hashref when used (it could also be autovifified to an arrayref this way).

Update – 01/Nov/2017 end

The following code declares a hash attribute, but when Object is created an array is given as reference.

package MooHash;

use Moo;
use feature 'say';
use Data::Dumper;

has os_hash => (
 is => 'rw',
 default => sub { {} },
);

1;
no Moo;

#### Main code
my @a = qw(foo bar);

my $z = MooHash->new(
 os_hash => \@a,
);

say Dumper $z->os_hash;
say $z->os_hash->[0];

 

Which gives us:

$VAR1 = [
 'foo',
 'bar'
 ];

foo

References:

1 – Anonymous Functions:

http://modernperlbooks.com/books/modern_perl/chapter_05.html

 

9 Comments

  1. choroba choroba

    See Type::Tiny on how to get Moose-like types into Moo. Also, for attributes that behave like arrays or hashes in Moose, see Moose::Meta::Attribute::Native.

    • Thanks,

      Since I have been coding in Perl OO old-style, using external modules such as Type::Tiny is kind of new to me. Sometimes it takes me a lot of time to figure out how to glue them together.

      I will read more about this Moose::Meta::Attribute::Native module.

    • I’ve been using MooX::Types::MooseLike::Base, myself.

      This step-by-step tutorial on creating types is interesting, but it’s more work than you really need to go through.

      • ;)

        I was trying to use Hash and Array by not adding any additional library.

        Thank you for the feedback.

  2. Grinnz Grinnz

    The defaults of `sub { () }` and `sub { }` are identical (there is no `()` operator) and just set the default to undef, since nothing is returned. Thus it’s autovivified to a hashref when used (it could also be autovifified to an arrayref this way). If you had Type::Tiny type checking prohibiting undef this would fail that check.

    • Thanks, I will improve this post.

      Thank you for the feedback.

  3. Haodemon Haodemon

    This is so lame Jesus why did I read this ughh

    • Well, you could give some useful feedback. I would improve my post. Thanks anyway.

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload the CAPTCHA.