NAME
Class::Slot - Simple, efficient, comple-time class declaration
VERSION
version 0.09
SYNOPSIS
package Point;
use Class::Slot;
use Types::Standard -types;
slot x => Int, rw => 1, req => 1;
slot y => Int, rw => 1, req => 1;
slot z => Int, rw => 1, def => 0;
1;
my $p = Point->new(x => 10, y => 20);
$p->x(30); # x is set to 30
$p->y; # 20
$p->z; # 0
DESCRIPTION
Similar to the fields pragma, slot
declares individual fields in a class, building a constructor and slot accessor methods.
Although not nearly as full-featured as other solutions, Class::Slot
is light-weight, fast, works with basic Perl objects, and imposes no dependencies outside of the Perl core distribution. Currently, only the unit tests require non-core packages.
Class::Slot
is intended for use with Perl's bare metal objects. It provides a simple mechanism for building accessor and constructor code at compile time.
It does not provide inheritance; that is done by setting @ISA
or via the base
or parent
pragmas.
It does not provide method wrappers; that is done with the SUPER
pseudo-class.
It does build a constructor method, new
, with support for default and required slots as keyword arguments and type validation of caller-supplied values.
It does build accesor methods (reader or combined reader/writer, using the slot's name) for each slot declared, with support for type validation.
@SLOTS
The @SLOTS
package variable is added to the declaring package and is a list of quoted slot identifiers. @SLOTS
includes all slots available to this class, including those defined in its ancestors.
CONSTRUCTOR
Class::Slot
generates a constructor method named new
. If there is already an existing method with that name, it may be overwritten, depending on the order of execution.
DECLARING SLOTS
The pragma itself accepts two positional parameters: the slot name and optional type. The type is validated during construction and in the setter, if the slot is read-write.
Slot names must be valid perl identifiers suitable for subroutine names. Types must be either a code ref which returns true for valid values or an instance of a class that supports the can_be_inlined
, inline_check
, and check
methods (see "Inlining methods" in Type::Tiny).
The slot
pragma may be used as either a keyword or a pragma. The following are equivalent:
use Class::Slot x => Int;
use slot x => Int;
slot x => Int;
A simple source filter is used to translate uses of slot
and use slot
into use Class::Slot
. This is a somewhat brittle solution to ensuring compile time code generation while avoiding a clash with Tie::Hash::KeysMask, which uses the slot
namespace internally but nevertheless holds the keys to it on CPAN.
As a result, care must be taken when defining slots using the slot name ...
syntax (rather than use Class::Slot name ...
). The source filter identifies the keyword slot
when it appears as the first value on a line, followed by a word boundary. There is the potential for false positives, such as with:
my @ots = qw(
slot blot glot clot
);
OPTIONS
rw
When true, the accessor method accepts a single parameter to modify the slot value. If the slot declares a type, the accessor will croak if the new value does not validate.
req
When true, this constructor will croak if the slot is missing from the named parameters passed to the constructor. If the slot also declares a default value, this attribute is moot.
def
When present, this value or code ref which returns a value is used as the default if the slot is missing from the named parameters passed to the constructor.
If the default is a code ref which generates a value and a type is specified, note that the code ref will be called during compilation to validate its type rather than re-validating it with every accessor call.
fwd
When present, generates delegate accessor methods that forward to a mapped method on the object stored in the slot. For example:
# Foo.pm
class Foo;
sub life{ 42 }
1;
# Bar.pm
class Bar;
use Class::Slot;
use parent 'Foo';
slot 'foo', fwd => ['life'];
1;
# main.pl
my $bar = Bar->new(foo => Foo->new);
say $bar->life; # calls $bar->foo->life
Alternately, fwd
may be defined as a hash ref mapping new local method names to method names in the delegate class:
# Bar.pm
class Bar;
use Class::Slot;
use parent 'Foo';
slot 'foo', fwd => {barlife => 'life'};
1;
# main.pl
my $bar = Bar->new(foo => Foo->new);
say $bar->barlife; # calls $bar->foo->life
say $bar->life; # dies: method not found
INHERITANCE
When a class declares a slot which is also declared in the parent class, the parent class' settings are overridden. Any options not included in the overriding class' slot declaration remain in effect in the child class.
package A;
use Class::Slot;
slot 'foo', rw => 1;
slot 'bar', req => 1, rw => 1;
1;
package B;
use Class::Slot;
use parent -norequire, 'A';
slot 'foo', req => 1; # B->foo is req, inherits rw
slot 'bar', rw => 0; # B->bar inherits req, but is no longer rw
1;
COMPILATION PHASES
BEGIN
slot
statements are evaluated by the perl interpreter at the earliest possible moment. At this time, Class::Slot
is still gathering slot declarations and the class is not fully assembled.
CHECK
All slots are assumed to be declared by the CHECK
phase. The first slot declaration adds a CHECK
block to the package that installs all generated accessor methods in the declaring class. This may additionally trigger any parent classes (identified by @ISA
) which are not yet complete.
RUNTIME
If CHECK
is not available (for example, because the class was generated in a string eval), the generated code will be evaluated at run-time the first time the class' new
method is called.
DEBUGGING
Adding use Class::Slot -debug
to your class will cause Class::Slot
to print the generated constructor and accessor code just before it is evaluated.
Adding use Class::Slot -debugall
anywhere will cause Class::Slot
to emit debug messages globally.
These may be set from the shell with the CLASS_SLOT_DEBUG
environmental variable.
PERFORMANCE
Class::Slot
is designed to be fast and have a low overhead. When available, Class::XSAccessor is used to generate the class accessors. This applies to slots that are not writable or are writable but have no declared type.
This behavior can be disabled by setting $Class::Slot::XS
to a falsey value, although this must be done in a BEGIN
block before declaring any slots, or by setting the environmental variable CLASS_SLOT_NO_XS
to a truthy value before the module is loaded.
A minimal benchmark on my admittedly underpowered system compares Moose, Moo, and Class::Slot. The test includes multiple setters using a mix of inherited, typed and untyped, attributes, which ammortizes the benefit of Class::XSAccessor to Moo and Class::Slot.
| Rate moo moose slot
| moo 355872/s -- -51% -63%
| moose 719424/s 102% -- -25%
| slot 961538/s 170% 34% --
Oddly, Moo seemed to perform better running the same test without Class::XSAccessor installed.
| Rate moo moose slot
| moo 377358/s -- -50% -56%
| moose 757576/s 101% -- -12%
| slot 862069/s 128% 14% --
AUTHOR
Jeff Ober <sysread@fastmail.fm>
COPYRIGHT AND LICENSE
This software is copyright (c) 2020 by Jeff Ober.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.