This is a rough draft of some thoughts I've had regarding Cor attributes declaration. Please leave your thoughts.
Part of the problem with the Cor object proposal for the Perl
code is that
we tended to use the semantics of has
as declared in the
Moose OO extension for Perl. Unfortunately,
this function handles:
- Data
- Attributes
- Types
- Coercion
- Delegation
- Clearers
- Predicates
- Documentation
- Constructor args
- Default values
- Overriding
- … and more!
It makes it very, very easy to declare attributes for Perl objects, but it's trying to do too much and has the wrong defaults. Instead, I've been rethinking this tremedously, trying to find a way to keep the "ease of use", but making it easier to do the right thing.
There have been suggestions that we separate slot (data) declaration from the slot's attribute declaration, but I think this is a mistake. We have literally hundreds of modules on the CPAN which try to join the two together. If we break them back apart, people will try to put them back together and Cor will again cause fragmentation of approaches on how to build objects.
My idea is to turn has
into a variable declarator similar to my
. It would
exist in the context of a Cor class block. Here's a minimal 2D point object
with x/y attributes, defaulting to 0 each, and with directly immutable
attributes (yes, it's a silly example):
class Point2D {
has [qw/x y/] :optional = (0,0);
# objects can mutate their own state
method move ($dX, $dY) {
$self->x($self->x + $dX);
$self->y($self->y + $dY);
}
}
The syntax is loosely:
has ::= 'hash' TYPE SLOTS ATTRIBUTES DEFAULT ';'
TYPE ::= # probably punting on this for an MVP
SLOTS ::= '[' SLOT {SLOT} ']' | SLOT
SLOT ::= SIGIL? IDENTIFIER
SIGIL ::= '$' | '@' | '%' | '*'
IDENTIFIER ::= [:alpha:] {[:alnum:]}
ATTRIBUTES ::= { ':' IDENTIFIER }
DEFAULT ::= '=' PERL_EXPRESSION
Slot behaviors:
- Read-only by default
- Defaults are lazy unless
:immediate
is provided - Have no accessor if a
SIGIL
is used
Current attributes:
:required
: slot must have a value at object construction:optional
: slot may have a value at object construction- Neither
:required
or:optional
: slot must not have a value at object construction :immediate
: default value is required and will be calculated at object construction:weak
: value is a weak ref.:builder
,:builder(name)
: A builder method (default:_build_$slot_name
) will provide the default:clearer
,:clearer(name)
: Reset slot toundef
or default value.:predicate
,:predicate(name)
: Test if slot has been set (but it may have been set toundef
):rw
: attribute is read-write (all classes are allowed to write their own data)handles(@|%)
: delegation
Examples:
class Box {
# all attributes are required to be passed to constructor
has [qw/height width depth/] :required;
# You can optionally name your box.
has 'name' :optional :predicate(has_name);
# cannot be set via constructor. Uses a lazy `_build_volume` method
has 'volume' :builder;
method _build_volumne {
return $self->height * $self->width * $self->depth;
}
}
Another example:
class Cache::LRU {
use Hash::Ordered;
# types probably won't be in v1
# sigil means that this attribute has no accessor. Hash::Ordered object is
# default
has Hash::Ordered $cache :handles(get) = Hash::Ordered->new;
# you may optionally pass in a max_size value to the constructor
# you can also call $cache->max_size to read this value or
# $cache->max_size($new_size) to mutate this value.
has PositiveInt :optional :rw max_size = 20;
# immediately record creation time
has created :immediate = time;
method set ( $key, $value ) {
if ( $cache->exists($key) ) {
$cache->delete($key);
}
elsif ( $cache->keys > $self->max_size ) {
# need the while loop in case they reset max size to a lower value
$cache->shift while $cache->keys > $self->max_size;
}
$cache->set( $key, $value ); # new values in front
}
}
@jhthorsen: we're moving house (so I'm packing) and I have to prep for going to Germany next week for a conference, so I'll be brief.
It turns out that having both
has foo
andhas $foo
was a mistake because, amongst other problems, it conflates slot declaration with accessor generation. However, here's a quick peek at some of what I'm currently working on. It's not perfect, buthas $x
now only declares an instance variable and I provide a "Moose" column to show the equivalent.Note that this example doesn't show all combinations.
:handles
,:weak
, and:predicate
are not included becausethey're allowed with anything. And because we strive for immutable objects, so
:writer
and:clearer
should be codesmells and thus aren't represented in the table below (though they will exist). Thus, here are the most common declarations we expect:
And suggestions for a better name for
:no-constructor
are welcome. Since all slots are, by default, private (with attributes to open them up),:private
doesn't quite seem right.