The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

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.