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
}
}
Shout out to some (not all) people who've been very helpful with the current work:
Update: And Aristotle (his real name!), who reminded me of the importance of backwards compatibility.