NAME
Zydeco::Manual::02_Attributes - Powerful and concise attribute definitions
SYNOPSIS
package MyApp {
use Zydeco;
class Person {
has name, age;
}
class Employee extends Person;
class Department {
has name;
has manager;
}
}
my $rd = MyApp->new_department(
name => "Research & Development",
manager => MyApp->new_person(name => "Bob"),
);
MANUAL
Objects store their data in attributes. Attributes in Perl are a slightly fuzzy concept, consisting of three interlinked components:
A slot to store per-object data.
Optionally, a parameter passed when instantiating the object to set the initial value for the data.
Optionally, methods to call to get/set the value of the data.
Declaring a Basic Attribute
To declare an attribute, use the has
keyword.
package MyApp {
use Zydeco;
class Person {
has name ( is => rw );
}
}
This creates a slot for the person's name. When you instantiate a Person object, you can pass in the name:
my $bob = MyApp->new_person( name => "Robert" );
The Person object will have a method called name
allowing you to get the person's name:
say $bob->name();
If passed a parameter, it will set a new name for the person:
$bob->name( "Bobby" );
In the above example, the slot, the parameter, and the accessor method all are all called "name". But it's possible to give the parameter and accessor method different names.
package MyApp {
use Zydeco;
class Person {
has name ( init_arg => "moniker", accessor => "fullname" );
}
}
my $bob = MyApp->new_person( moniker => "Robert Jones" );
say $bob->fullname();
It's possible to declare different method names for the getter and setter methods:
package MyApp {
use Zydeco;
class Person {
has name (
init_arg => "moniker",
reader => "get_fullname",
writer => "set_fullname",
);
}
}
my $bob = MyApp->new_person();
$bob->set_fullname( "Robert Jones" );
say $bob->get_fullname();
The is
option provides shortcuts for init_arg
, accessor
, reader
, and writer
.
# init_arg => "foo", accessor => "foo"
has foo ( is => rw );
# init_arg => "foo", reader => "foo"
has foo ( is => ro );
# init_arg => "foo", reader => "foo", writer => "_set_foo"
has foo ( is => rwp );
# init_arg => "foo",
has foo ( is => bare );
# no init_arg or accessor methods created!
has foo ( is => private );
Zydeco defaults to is => ro
. Making attributes read-only can save many headaches.
Required Attributes
An attribute may be marked as required using an exclamation mark.
package MyApp {
use Zydeco;
class Person {
has name!;
}
}
This is a shorthand for the longer:
package MyApp {
use Zydeco;
class Person {
has name ( required => true );
}
}
When multiple attributes are declared with a single has
keyword, the specification in parentheses applies to them all.
package MyApp {
use Zydeco;
class Person {
# name and age are both required
has name, age ( required => true );
}
}
Using the exclamation mark allows you to declare a mixture of required and non-required attributes with a single has
statement.
If an attribute is required, then you must provide a value for it when instantiating the object. This does not mean that the data cannot be cleared later.
Predicates and Clearers
As well as getter/setter accessors, it's possible to create predicate and clearer accessors.
package MyApp {
use Zydeco;
class Person {
has name (
reader => "get_name",
writer => "set_name",
predicate => "has_name",
clearer => "clear_name",
);
}
}
If $bob->clear_name
is called, $bob->get_name
will no longer have a name, and $bob->get_name
will return undef
.
There is a difference between:
$bob->clear_name;
$bob->set_name( undef );
In the second case, $bob
still has a name, even if that name is an undefined value. $bob->get_name
will return undef
for both, but $bob->has_name
will return true in the second case.
As implied earlier, with a clearer
, even a required attribute's value can be cleared.
Zydeco offers shortcuts clearer => true
and predicate => true
to use default names for clearer and predicate methods. These are just the same as the attribute name, but with "clear_" and "has_" prefixed.
Defaults and Builders
It is possible to set a default value for an attribute.
package MyApp {
use Zydeco;
class Person {
has name ( is => rw ) = "Anonymous";
}
}
my $bob = MyApp->new_person;
say $bob->name; # ==> Anonymous
$bob->name( "Robert" );
say $bob->name; # ==> Robert
There are two main styles of defaults: eager and lazy. Eager defaults will be applied when the object is instantiated. Lazy defaults will be applied when the attribute is first read.
If an attribute is cleared with the clearer, a lazy default will be applied again next time it is read, but an eager default will not.
Predicates (like has_name
) will return false for a lazy attribute if the default has not been applied yet.
Zydeco allows defaults to be given in a variety of ways. The basic way is to set a default
option in the attribute specification. This only works for non-reference values (undef, numbers, and strings).
class Person {
has name (
is => rw,
default => "Anonymous",
);
}
For more complex values, that needs to be wrapped in a coderef.
class Person {
has name_parts (
is => rw,
default => sub { return ["Joe", "Anonymous"] },
);
}
A common pattern is to use one attribute to help set the default for another.
class Person {
has name! ( is => rw );
has name_parts (
is => rw,
lazy => true,
default => sub {
my $self = shift;
return [ split / /, $self->name ];
},
);
}
Note that during object instantiation, there is no guaranteed order which attributes will be processed, so to prevent name_parts
's default from being processed before name
is ready, it is made lazy.
As a shortcut, Zydeco allows defaults to be given using an equals sign.
class Person {
has name!;
has name_parts = [split / /, $self->name];
}
In this case, Zydeco will automatically decide whether your default needs to be lazy, and if usually correct in its choice, however you can override it.
class Person {
has name!;
has name_parts ( lazy => false ) = [split / /, $self->name];
}
A common pattern is to use a method call to build the default:
class Person {
has name!;
has name_parts = $self->_build_name_parts;
method _build_name_parts {
return [ split / /, $self->name ];
}
}
This pattern is useful because a subclass of "Person" can override the "_build_name_parts" method easily.
Because this pattern is useful, there's a shortcut for it:
class Person {
has name!;
has name_parts ( builder => "_build_name_parts", lazy => true );
method _build_name_parts { ... }
}
Or if you are happy to rely on the default name (prefix "_build_"):
class Person {
has name!;
has name_parts ( builder => true, lazy => true );
method _build_name_parts { ... }
}
Or even just:
class Person {
has name!;
has name_parts ( is => lazy );
method _build_name_parts { ... }
}
Triggers
Notice that in the above examples, "name_parts" is built from "name". If the person's name is changed, their name_parts are no longer correct. We can fix this using a trigger on "name". A trigger is a coderef that gets called when the value of an attribute is set.
class Person {
has name (
is => rw,
required => true,
trigger => sub {
my $self = shift;
$self->clear_name_parts;
}
);
has name_parts ( is => lazy, clearer => true );
method _build_name_parts {
return [ split / /, $self->name ];
}
}
You can use a method for a trigger.
class Person {
has name (
is => rw,
required => true,
trigger => "_trigger_name"
);
has name_parts ( is => lazy, clearer => true );
method _trigger_name {
$self->clear_name_parts;
}
method _build_name_parts {
return [ split / /, $self->name ];
}
}
The trigger => true
shortcut works if you're happy to rely on the default method name for a trigger. (Prefix "_trigger_".)
Attributes in Roles
It is worth mentioning that roles can have attributes too.
role Nameable {
has name ( is => lazy );
}
class Person with Nameable {
method _build_name {
return "Anonymous";
}
}
param
and field
As of version 0.615, Zydeco supports param
and field
as almost-synonyms for has
, which are analagous to the keywords provided by MooseX::Extended. They have the following semantics:
param
-
This indicates that the attribute is expected to be set in the constructor. Unless a default or builder is provided, or you explicitly specify
required => false
, this attribute will be required. field
-
This indicates that the attribute should not be set in the constructor. Unless you explicitly specify an
init_arg
it will beundef
, and if you do explicitly specify aninit_arg
it muse start with an underscore.If there's no default or builder and the attibute is read-only, then a writer
_set_$attribute
will be provided.
KEYWORDS
In this chapter, we looked at the following keywords:
has
param
field
NEXT STEPS
We have already introduced a few methods into our classes as builders and triggers. In the next chapter we dive further into adding methods to give our objects behaviours.
Zydeco::Manual::03_MethodsSignaturesTypesCoercion - Methods with signatures, type constraints, and coercion
AUTHOR
Toby Inkster <tobyink@cpan.org>.
COPYRIGHT AND LICENCE
This software is copyright (c) 2020-2022 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.