Programming with Moose/Syntax/has
The most important Moose keyword is arguably "has", which is essentially the most beefed up accessor-generator ever devised. Many of these features can be found in the accompanying docs of Moose, and Class::MOP::Accessor.
Attributes of has
[edit | edit source]Below you can find all of the attributes of has
.[1] Asterisk represents the default.
[+]has => (
isa => $type_constraint,
is => 'ro|rw',
coerce => 0*|1,
weak_ref => 0*|1,
does => $role_name,
required => 0*|1
lazy => 0*|1
auto_deref => 0*|1
init_arg => $str|undef,
default => $default_value|sub { $default_value },
metaclass => $voodoo,
trigger => $sub_ref,
initializer=> $sub_ref,
handles => Array|Hash|Regex|Role|Code,
lazy_build => 0*|1,
clearer => $clearer_name,
predicate => $predicate_name,
builder => $builder_name,
reader => $reader_name,
writer => $writer_name,
);
isa
[edit | edit source]The second most essential attribute of has
is isa
. isa
is pronounced "Is a", and essentially does little more than tell what the attribute can be set with.
Any Item Bool Undef Defined Value Num Int Str ClassName Ref ScalarRef ArrayRef HashRef CodeRef RegexpRef GlobRef FileHandle Object Role
These are the default choices for isa
. In addition to them isa
can be set to any class name, for instance isa => "URI"
, or isa => "HTML::Treebuilder"
.
has 'foobar' => (
isa => 'Str',
is => 'ro',
);
has 'foobar' => (
isa => 'MooseObject',
is => 'ro',
);
Moose v0.26 introduces parameterized types[1]—no one knows what they really do, but they gave the ability to do such things as:
has 'foobar' => (
isa => 'ArrayRef[URI]'
);
Now when you try to store to ->foobar
, you had better only add URI elements to the ArrayRef, or you will croak
.
is
[edit | edit source]The heart of the attribute generating has
is the attribute is
. If something is is => 'ro'
the user has a getter (read-only accessor) by the specified name. Conversely, if the value supplied is is => 'rw'
the user has a getter/setter hybrid (read-write accessor).
We place is after isa because these two are the most commonly used attributes to has . If in this order, and if you use PBPs style of commas-left they will align and your code will make Rubyists drivel. You can promptly switch it back to obfuscated mode when they walk away. |
The default value is to not generate an accessor of either kind at all.
coerce
[edit | edit source]The coerce
attribute to has is significantly more complex than is
and isa
. It tells Moose, "Hey dude, it would be really l33t if you could get this value into the datatype I want it to be without being a pain in the ass all the time."
Moose generally listens when you talk to it as such.
package MyMoose;
use Moose;
use Moose::Util::TypeConstraints;
use URI;
subtype 'URI'
=> as 'Object'
=> where { $_->isa('URI') };
coerce 'URI'
=> from 'Str'
=> via { URI->new($_) };
has 'myuri' => ( is => 'rw', isa => 'URI', coerce => 1 );
package main;
my $m = MyMoose->new;
$m->myuri( 'foobar' );
print ref $m->myuri; ## print URI::_generic
To use coerce
you must have a supplied a subtype which can coerce.
weak_ref
[edit | edit source]You cannot use coerce => 1
, if you set weak_ref => 1
Weak_ref stops the refcount held in the attribute from being incremented by the object. This means if you have one copy of something, and a hundred objects use it with weak_ref => 1
, you can successfully destroy their copies by undef
'ing your one copy. Perl's garbage collector will destroy the item when its reference count reaches zero. With weak_ref
your object will never increase the reference count. This prevents circular recursion, but leaves you susceptible to having the rug pulled out from under you, so to speak.
package MyMoose;
use Moose;
has 'foo' => (
isa => 'ArrayRef',
is => 'ro',
weak_ref => '1',
);
package main;
my $foo = [qw/ foo bar baz/];
my $m = MyMoose->new({ foo => $foo });
print @{ $m->foo }; ## prints foobarbaz
undef $foo;
## Can't use an undefined value as an ARRAY reference at test.pl line 21.
print @{ $m->foo };
does
[edit | edit source]This will accept the name of a role which the value stored in this attribute is expected to have consumed.
This section is a stub. You can help Wikibooks by expanding it. |
required
[edit | edit source]That which is required is required => 1
. All things required must have a value specified in a hash whose ref is sent to ->new
.
package MyMoose;
use Moose;
has 'foo' => ( required => 1 );
package main;
my $m1 = MyMoose->new(); ## dies with error pinpointing cause
my $m2 = MyMoose->new({ foo => 4 }); ## Blisses in the fact the user is not a git
lazy
[edit | edit source]Don't want something to happen at compile time... Defer it! Lazy necessitates a default: you must supply one.
This one attribute introduces a whole new way to model problems. It brings a new mentality, don't do work, until it needs to get done. About time computer programs grew the ability to procrastinate without stressing me out. I'd hate to restrict a luxury of that caliber to myself.
Think of the lazy/default combination in terms of a sub that stores its return value so it doesn't have to recompute it on subsequent calls.
In this example foobar
, does not get set until you ask for it; and, in this example it pulls down a webpage, and thus we would not want to do this possibly time-consuming operation until we know we need the result.
sub build_foobar { return get( 'http://www.example.com/somedatafeed' ); }
has 'foobar' => (
isa => Int,
is => 'rw',
lazy => 1,
default => \&build_foobar,
);
The prefix of build_ to all subs used in default is a Moose convention, and not just an oddity of this tutorial -- some features are dependent on it. |
auto_deref
[edit | edit source]Auto_deref is a tricky and deceitful little fellow. It doesn't fix perl, it only makes things slightly more simple. This attribute simply returns an expanded Array for an ArrayRef, or a Hash for a HashRef. To use auto_deref you must have isa=>ArrayRef
, or isa=>HashRef
or a Parameterized type of one of the two: isa=>ArrayRef[Int]
has 'foo' => ( isa => 'HashRef', is => 'rw', auto_deref => 1 );
What did I mean by deceitful well auto_deref still just simply passes by copy.
package Class;
use Moose;
has 'foo' => ( isa => 'ArrayRef', is => 'rw', auto_deref => 1 );
my $c = Class->new({ foo => [qw/foo bar baz/] });
## Uses auto_deref
s/.*// for $c->foo;
print $_ for $c->foo; # foo bar baz
## Uses manual deref
s/.*// for @{$c->foo};
print $_ for $c->foo; # Nada.
default
[edit | edit source]Default has two radically different functions:
- set a default static value.
- set a dynamic lazy (
lazy=>1
) default value on the first call to the accessor
The first and most simple application of default, does nothing more than set a fall-back static value:
package Person;
use Moose;
has 'name' => (
isa => 'Str',
is => 'rw',
default => "I'm too stupid to fill out a form",
);
package main;
my $p = Person->new({ name => $_ });
print 'Greetings' . $p->name;
The second implementation of default is much more complex. Using this method, you state the attribute as lazy => 1
(do nothing at compile-time), and then you point the default to a function such as sub { DBI->connect }
. When the function is called, it sees the slot is unset (fails a predicate test) and the sub
pointed to by default is called setting the slot with the return value. This value is cached away and will not be recomputed unless the slot again becomes unset—as in the case of calling clearer.
metaclass
[edit | edit source] This section is a stub. You can help Wikibooks by expanding it. |
clearer
[edit | edit source]If you ever need to undo all changes made to a value clearer is a great way to go about it. Let's say you have made the first call to a lazy => 1 attribute, which sets the initial value to the corresponding default. Let's assume you don't like this value, and you want to reset it as if it was never called. Calling the clearer-specified method in this context will accomplish that. Internally, clearer
deletes the slot.
If you call an accessor on a clear
ed value it will return undef, unless there is a lazy/default combo specified. In which case it will reinitilize as if it was the first call. If you call clearer on a regular default, the value might appear to be set to undef. In effect the slot is not existence. To test for this see predicate.
package MyMoose;
use Moose;
has 'foo' => (
isa => 'Int',
is => 'rw',
clearer => 'clear_foo',
);
package main;
my $m = MyMoose->new;
$m->foo(5);
$m->clear_foo;
print "defined" if defined $m->foo;
print "exists" if exists $m->{foo};
print $m->{foo};
Use of uninitialized value in print at test.pl line 22.
Don't ever access a Moose object's underlying hash unless writing fugly examples for books. Moose is supposed to abstract away what it is blessed. Don't remind people that the hash even exists.
predicate
[edit | edit source]All predicate does is create a sub that returns true (1) if the attribute's respective slot exists, and false (0) if it doesn't. It's a simple convenience attribute. Without predicate => 'name'
, you would not easily be able to discern whether or not the slot exists without violating the black-box tenet of Object Orientation. Predicate makes this step easy, and allows you to continue without peering into the Moose internals. Predicate is only useful for:
- Inter-operating with its family-function
clearer->
—which deletes the slot. Predicate can be though of as clearer_test in this context. - Testing to see if a non-required attribute was supplied to the constructor.[2]
- Testing to see if a
lazy => 1
with adefault
has fired yet
Predicate takes a string which it uses to generate the predicate-sub's name:
predicate => 'my_predicate_sub'
Here we will see it in action:
package MyMoose;
use Moose;
# To accept undef also as a valid data.
has 'ulcer' => (
isa => 'Str|Undef',
is => 'rw',
predicate => 'is_sick',
);
package main;
my $m = MyMoose->new({ ulcer => 'painful' });
print $m->is_sick # true
## User doesn't know affliction
## Here Ulcer description isn't defined but we still have an ulcer
my $m = MyMoose->new({ ulcer => undef });
print $m->is_sick # true
trigger
[edit | edit source]A trigger is a function that gets called after you write to an accessor or when you set a value using the constructor. However, as of the time of writing they do not fire on attributes set with lazy_build, default, or builder. A trigger is set with a CodeRef, which receives the following values: $self, $value, $oldValue.
use Moose;
sub shout { print 'Moosegasm' }
has 'foo' => ( is => 'rw', trigger => \&shout );
If you want a trigger to only execute when you run the setter try something more on the lines of:[3]
after 'foo' => sub {};
after $self->meta->get_attribute('foo')->get_write_method => sub {}
If you want a trigger to fire on something that is built from a builder/default/lazy_build you will either have to explicitly unroll the trigger in the builder/default/lazy_build code, or hack a wrapper around the builder. This is because the trigger can presume the attribute is set and read from it. But, Moose only sets the attribute after the builder has returned—so you can't simply call the trigger from within the builder.
around '_build_foo' => sub {
my ( $sub, $self, @args ) = @_;
my $foo = $self->$sub;
$self->foo( $foo );
$self->_init_foo;
$foo;
};
init_arg
[edit | edit source]This attribute serves two distinct purposes:
- It can alter the key from the constructor's hash that Moose will use use to set your attribute.
- You can ignore any values sent in the constructor by setting to it to undef.
You can tell Moose to use a different key in the constructor's hash (->new( $constructorsHash )
) by explicitly setting init_arg to something other than the attribute name. For example:
package Class;
use Moose;
has 'foo' => ( isa => 'Str', is => 'rw', init_arg => 'bar' );
package main;
say Class->new({ bar => "BarValue" })->foo ## outputs "BarValue"
Or, you can have Moose ignore the constructor's hash. This will even work with a default.
package Class;
use Moose;
has 'foo' => ( isa => 'Bool', is => 'rw', default => 1, init_arg => undef );
package main;
say Class->new({ foo => 0 })->foo; ## returns true
This attribute can not be inherited and modified with +attr:
package Class;
use Moose::Role;
has 'foo' => ( isa => "Int", is => "ro" );
has "+foo" => (init_arg => "FoO");
Class->new({FoO=>1})
initializer
[edit | edit source]This feature assists in initializing (setting the slot). It fires when one of the following are met:
- A default is present
- A value is sent in the constructor
- A value is computed from lazy_build
- A value is computed from a builder
- Or, a triggering through the meta such as Class::MOP::Attribute's set_initial_value
If you don't set the slot in the initializer, the slot will effectively be set to undef, assuming the type system permits it.
The initializer sub receives four values: $self, $value, $writerSubRef, $attributeMeta. With $attributeMeta probably being an instance of Class::MOP::Attribute. The $writerSub is not a method.
One very important point about an initializer is it fires *after* the type system. The value must pass through a type coercion or be a valid type prior to the initializer firing.
package MooseClass;
use Moose;
has "foo" => (
isa => 'Value',
is => 'rw',
initializer => sub {
my ( $self, $value, $writer_sub_ref, $attribute_meta ) = @_;
$writer_sub_ref->($value);
}
);
my $c = MooseClass->new( { foo => 5 } );
say $c->foo;
lazy_build
[edit | edit source]Lazy_build is an attribute that is made to simplify quick construction of a class. Here is what comprises it:[4]
#If your attribute name starts with an underscore:
has '_foo' => (lazy_build => 1);
#is the same as
has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', builder => '_build__foo');
# or
has '_foo' => (lazy => 1, required => 1, predicate => '_has_foo', clearer => '_clear_foo', default => sub{shift->_build__foo});
#If your attribute name does not start with an underscore:
has 'foo' => (lazy_build => 1);
#is the same as
has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', builder => '_build_foo');
# or
has 'foo' => (lazy => 1, required => 1, predicate => 'has_foo', clearer => 'clear_foo', default => sub{shift->_build_foo});
handles
[edit | edit source]This feature provides another way to delegate methods to a different module.
The handles attribute can be configured in a different ways with varying degrees of syntactic sugar:
- Array
- Hash
- Regex
Here is an example from HTML::TreeBuilderX::ASP_NET which uses the Regex configuration:
has 'hrf' => (
isa => 'HTTP::Request::Form',
is => 'ro',
handles => qr/.*/,
lazy_build => 1,
);
A good and general use case is handles => qr/.*/, which tells Moose to defer to the module specified in isa for all functions which it declares and aren't overridden in your package. This will often allow you to avoid subclassing and munging ->new.
traits
[edit | edit source]Traits, or more specifically Attribute Traits, are a mechanism for expanding the functionality of attributes.
An example of this would be the Moose functionality provided by the Native traits. These will provide helper methods for all container types: Hash, Array; as well as the strongly typed Number, String, Bool, Couter, and Code. The Native traits have functionality that can be turned on as required through an additional handles attribute.
has 'attributes' => (
isa => 'HashRef',
traits => ['Hash'],
is => 'ro',
handles => { get_attr => 'get' },
);
Clearing up confusion
[edit | edit source]initializer vs default
[edit | edit source]Initializers are part of the default process. The layer that sets the default, is the same layer that fires the initializer. A default value initializes a slot to a set value. The fire order of defined and initializer is defined for an attribute; but, totally undefined amongst all of the modules attributes.
Defined for an attribute:
has 'foo' => (
isa => 'Str'
, is => 'rw'
, default => 'foobarbaz'
, initializer => sub {
my ( $self, $arg, $writerSub, $attributeMeta ) = @_;
print $arg; # prints 'foobarbaz'
}
);
Undefined for set of attributes: one attribute (foo) might fire its initialer, write to another attribute (bar), just to have (bar) overwritten by its own default.
has 'bar' => ( isa => 'Str', is => 'rw', default => 'bar' )
has 'foo' => (
isa => 'Str'
, is => 'rw'
, default => 'foobarbaz'
, initializer => sub {
my ( $self, $arg, $writerSub, $attributeMeta ) = @_;
$self->bar('foo');
}
);
...
print MooseObject->new->bar ## prints bar
initializer vs trigger
[edit | edit source]Arguments:
Both trigger and initializer utilize $self, $value, and $attributeMeta.
- trigger
- $self, $value, $attributeMeta.
- initializer
- $self, $value, $writerSub, $attributeMeta.
Firing order:
It might be considered a bug that triggers do not fire in any event that writes to a slot.
condition | example | trigger | initializer |
---|---|---|---|
constructor | Class->new({foo=>1}) | fires | fires |
lazy_build | Class->new->foo | nada | fires |
builder | Class->new | nada | fires |
default | Class->new | nada | fires |
runtime explicit | Class->new->foo(5) | fires | nada |
Coexisting:
If a trigger is available, initializer will always fire before the trigger. However, it still does not fire when a value is explicitly set to the slot in runtime.
You can use trigger to have a sub fire when a value is explicitly provided in runtime. The downside to this approach is a double fire when the value is constructor-provided: one from trigger, one from initializer.
has 'foo' => ( isa => 'Value', is => 'rw', initializer => \&bar, trigger => \&bar );
sub bar { stuff }
Footnotes
[edit | edit source]- ^ It is almost impossible to be certain we have them all listed because of the inheritance of Moose, and the lack of external documentation. For instance, no where in the official Moose docs does it mention
->clear
or->predicate
; but, they're documented inClass::MOP::Attribute
.
References
[edit | edit source]- ↑ Little, Stevan (2007-09-27). "Changes/Revision History". Retrieved 2007-10-20.
- ↑ well required ones too, but if they aren't supplied, you die()
- ↑ Thanks to irc.perl.org's Sartak for info
- ↑ Little, Stevan. "Moose::Meta::Attribute". Moose 0.32. Infinity Interactive.
{{cite web}}
: Unknown parameter|access_date=
ignored (help); Unknown parameter|coauthors=
ignored (|author=
suggested) (help)