Section 6: Fields
August 16, 2023 ยท View on GitHub
Prev: Class Construction
Next: Methods
Section 6: Fields
This file is automatically generated. If you wish to submit a PR, do not
edit this file directly. Please edit
templates/rfc/attributes.md instead. Use bin/generate_rfc.pl to regenerate the RFCs.
6.1 Overview
For Corinna, declaring class and instance data is done with field.
field $x;
field $name;
This keyword does nothing but let the class access that instance data. It has
absolutely no other behavior. In Moo/se, the field function is named has
and provides tons of different behavior, some of which later turned out to
be a bad idea, such as lazy_build, which adds a several methods you may not
need and possibly didn't realize you were asking for.
In Corinna, additional behavior is defined via field attributes and these have been carefully designed to be as composable as possible. It's very difficult to create any combination of "illegal" attributes.
The minimal MVP grammar for fields and attributes can be found here.
To visualize that, let's look at the fields and their attributes from the
Cache::LRU class described in our overview.
field $num_caches :common = 0;field $cache :handles(exists, delete) = Hash::Ordered->new;field $max_size :param :reader = 20;field $created :reader = time;
The first field is class data. This is data shared by all instances of this class (and by the class itself). Note that it's initialized with any default value as soon as the class is compiled.
The second field holds an instance of Hash::Ordered and the
handles(exists, delete) says "delegate these methods to the object
contained in $cache".
The third field uses :param to say "you may pass this as a parameter to
the contructor", however, the = 20 tells us that this will be the default if
not passed. If there is no default, :param means we must pass this value to
the contructor.
The fourth field should, at this point, be self-explanatory, but its value is
always initialized at instantiation time, ensuring that every instance has its
value of $created calculated separately.
6.2 Field Creation
Field creation done by declaring field, field variable, and an optional
default value.
field $answer = 42; # instance data, defaults to 42
field %results_for; # instance data, no default
field @colors :common = qw(red green blue); # class data, default to qw(red green blue)
For scalar fields declared with field (and only for scalar fields), you can add
attributes after the declaration and before the optional default, if any.
For class data, for the MVP, only the :reader parameter may be used with
:common. This is because all instances share this data and using :param
and :writer to mutate it causes global action-at-a-distance. If you want
that behavior, you must write methods to support it. We do this to ensure that
you think carefully about this decision.
Note that all fields are completely encapsulated, but if they're exposed to the
outside world via :reader, :writer, or some other parameter, their name
defaults to the variable name, minus the leading punctuation. This will become
more clear as you read about the individual attributes.
If field name generation would cause another method to be overwritten, this is a compile-time error (unless we can later think of an easy syntax for specifying an override).
6.2.1 Field Initialization
Note that all fields are initialized from top to bottom. So you can do this:
field $x :param = 42;
field $answer = $x;
:common fields with defaults will be initialized at compile time, while
all instance attributes will be initialized at object construction.
6.2.2 Field Destruction
When an instance goes out of scope, instance fields will be destroyed in reverse order of declaration. When a class goes out of scope (currently only in global destruction), the same is true for class fields.
6.2.3 Field Attributes
The attributes we support for the MVP are as follows. Only variables declared
with field may take attributes. Note that while we allow field variables to
be something other than a scalar, those other fields can only use the
:common attribute.
6.2.3.1 :param(optional_identifier)
This value for this field may be passed in the constructor. If there is no
default via = ... on the field definition, this value must be passed to the
constructor. If you wish for it to be optional, but not have a default value,
use the = undef default.
If optional_identifier is present in parenthesis, this must be a legal Perl
identifier and will be used as the parameter name.
class Soldier {
field $id :param; # required in constructor
field $name :param = undef; # optional in constructor
field $rank :param = 'recruit'; # optional in constructor, defaults to 'recruit'
field $serial_number :param(sn);
}
# usage
my $thing = Soldier->new(
id => $required,
name => $optional_name, # this k/v pair can be omitted entirely
rank => $optional_rank,
sn => $some_value,
);
Note that in the above, passing serial_number to the constructor is an
error.
Because field names generate fatal errors if they would redefine another method, parent and child classes must have distinct constructor arguments.
6.2.3.2 :reader(optional_identifier)
By default, all fields are private to the class. You may optionally expose a
field for reading by providing a :reader attribute. You may specify an
optional name, if desired.
class SomeClass {
field $id :param :reader;
field $name :param = undef;
field $serial_number :param(sn) :reader(serial);
}
my $thing = SomeClass->new(...)
say $thing->id;
say $thing->serial;
say $thing->name; # no such method error
6.2.3.3 :writer(optional_identifier)
By default, all fields are private to the class. You may optionally expose
a field for writing by providing a :writer attribute. You may specify an optional
name, if desired. Note that a :writer has set_ prepended to the name. If you explicity
set the name of the writer to the name of the field, there will be a special
case to allow ->method for reading and ->method($new_value) for writing:
class SomeClass {
field $id :param :writer;
field $name :param = undef;
field $serial_number :reader :writer(serial);
}
my $thing = SomeClass->new(...);
$thing->set_id($new_id);
$thing->serial($new_serial);
say $thing->serial_number;
$thing->id($new_id); # no such method error
6.2.3.4 :predicate(optional_identifier)
Generates a has_$name predicate method to let you know if the field value has
been defined. Of course, you may change the name.
class SomeClass {
field $id :predicate(is_initialized) :param = undef;
field $name :predicate :param = undef;
field $serial_number;
}
my $thing = SomeClass->new(...);
if ( $thing->is_initialized ) { ... }
if ( $thing->has_name ) { ... }
6.2.3.5 :name(optional_identifier)
By default, the name of a field is the name of the variable minus the
punctuation. However, this name might be unsuitable for public exposure, or
may conflict with a parent class's methods. Use name(optional_identifier) to
set a new name for the field. Of course, you can always use
optional_identifier with the other attributes to change their names
individually.
field $id :name(ident) # name is now "ident"
:reader # ->ident()
:writer # ->set_ident($value)
:predicate(is_registered); # ->is_registered
6.2.3.6 :handles(...)
This attribute is used to delegate methods to the object contained in this field. You may pass it either a list of IDENTIFIERs and/or IDENTIFIER:IDENTIFIER mappings.
6.2.3.6.1 List of Identifiers and Identifier:Identifier Mappings
A list of identifiers says "these methods will be handled by this object."
use DateTime;
field $datetime :handles(now, today) = 'DateTime';
Now, when you call ->now or ->today on the object, those will be
delegated to the DateTime class. Note that because class names are not
"first class" in Perl, we regrettably treat the classname as a string.
You can rename delegated methods by providing identifiers for the method the
field value will handle and for the delegated object's original method name,
separated by a colon. In the following example, we are renaming exists to
has_key, but retaining the delete method.
use Hash::Ordered;
field $cache :handles(
has_key:exists, delete
) = Hash::Ordered->new;
6.3.1.1 :common
The :commmon attribute allows a field variable to be declared at the class
level (e.g., "class data") and its value is shared across all instances. Be
aware that its use, while not discouraged, effectively becomes "global data"
for the class and as such may be dangerous to use due to one instance changing
the value and other instances have already taken decisions based on the old
value. Thus, we recommend that class data generally be "read only."
field $foo :commmon;
field @colors :common = qw/red blue green/;
Prev: Class Construction
Next: Methods