Modern classes#
The class feature is the
recommended way to write object-oriented code in pperl. It replaces
the hand-rolled constructor, the manual accessor generation, the
hash-slot bookkeeping, and the use parent dance with a single
declarative syntax. You describe what the class is; the runtime
arranges how it is implemented.
This chapter walks through the feature as a working programmer would meet it: first a minimum class, then fields and attributes, then methods, then construction-time hooks, then inheritance. Each section is short on purpose; when you want the full surface of a keyword, follow the cross-link into its reference page.
Enabling the feature#
The feature is flagged experimental in Perl 5.42 and pperl mirrors that status. Every file that uses it opens with:
use v5.38;
use feature 'class';
no warnings 'experimental::class';
The use v5.38 line already enables the class feature on its
own, but being explicit makes the dependency visible to anyone
reading the file.
A minimal class#
use v5.38;
use feature 'class';
no warnings 'experimental::class';
class Point {
field $x :param;
field $y :param;
method distance_from_origin {
return sqrt($x ** 2 + $y ** 2);
}
}
my $p = Point->new(x => 3, y => 4);
say $p->distance_from_origin; # 5
Three things to notice:
There is no
sub new. The runtime generates the constructor from thefielddeclarations. A user-writtensub newinside aclassbody is a compile-time error on purpose — the generated one knows how to wire up:param, run initialisers, and triggerADJUSTblocks in the right order.Fields are referenced by their declared name inside methods, not through
$self->{x}. The instance storage is genuinely private; no code outside the class body can poke at it.The
methodkeyword takes care of$self. You never writemy $self = shift.
Fields#
A field is per-instance storage.
Every object of the class gets its own slot; every method and every
ADJUST block sees the field as if it were a lexical.
class Counter {
field $value = 0;
field $step = 1;
method tick { $value += $step }
method value { $value }
}
Fields accept scalars, arrays, and hashes:
class Registry {
field $name;
field @items;
field %seen;
}
Attributes on fields#
Field attributes take the declarative work off your hands.
:param— bind this field to a constructor argument. If the caller omits it, the field is either left unset or takes the declared default.:param(alt)— bind to a different constructor name.:reader— generate a read-only accessor with the field’s name minus the sigil.:reader(custom)— generate a reader under a different name.:writer— generate a setter (set_FIELDNAME).
class User {
field $name :param :reader;
field $email :param :reader :writer;
field $role :param :reader = 'guest';
}
my $u = User->new(name => 'Ada', email => 'ada@example.org');
say $u->name; # Ada
$u->set_email('ada@analytical.org');
say $u->role; # guest
Default values#
Defaults use one of three operators, each with a different rule:
=— apply when the caller omitted the parameter entirely.//=— also apply when the caller passedundef.||=— also apply when the caller passed any false value.
class Window {
field $title :param = 'untitled';
field $width :param //= 640;
field $shown :param ||= 1;
}
Pick = unless you have a concrete reason to allow the stronger
forms. Silent promotion of undef or 0 to a default is a bug
magnet.
Fields are not lexicals in the usual sense#
Fields look like lexicals inside a method body, but they have tighter rules:
You cannot
oura field, you cannotlocalone, and you cannot take a reference to one and export it.Fields are private to the class. Subclasses do not inherit a parent’s fields; if a subclass needs a parent’s state, it asks for it through a method.
A field’s initialiser runs before
$selfis bound. Insidefield $x = EXPR, you can use earlier fields by name and you can use__CLASS__, but you cannot use$self. Anything that needs the fully constructed instance belongs in anADJUSTblock.
Methods#
A method is a subroutine that
auto-binds $self and reads the enclosing class’s fields by name.
class File {
field $path :param :reader;
method slurp {
open my $fh, '<', $path or die "open $path: $!";
local $/;
return <$fh>;
}
method rename ($new) {
rename $path, $new or die "rename: $!";
$path = $new;
}
}
Methods use signatures automatically — use feature 'signatures'
is implied inside a class body. $self is the method’s implicit
first argument and does not appear in the signature.
Private methods#
A method declared with my is lexical to the class body and is
invoked through the ->& operator:
class Safe {
field $secret :param;
my method check ($n) { $n == $secret }
method unlock ($n) {
return $self->&check($n) ? 'open' : 'denied';
}
}
my method is the closest pperl has to a hard-private method; no
external caller can reach it because the name does not live in the
package at all.
Construction hooks: ADJUST#
An ADJUST block runs after field initialisation, with $self
already bound. It is the place for:
Post-construction validation.
Derived fields that depend on other fields.
Opening a handle or connecting to a resource whose lifetime matches the object.
class TempFile {
field $prefix :param = 'tmp';
field $path;
field $fh;
ADJUST {
$path = "/tmp/$prefix-$$-" . int(rand 1_000_000);
open $fh, '>', $path or die "open $path: $!";
}
method write ($data) { print {$fh} $data }
method path { $path }
}
Multiple ADJUST blocks in one class run in declaration order. In
an inheritance chain, the parent’s ADJUST blocks run before the
child’s.
Inheritance#
A class inherits from at most one parent using the :isa
attribute:
class Shape {
field $colour :param :reader = 'black';
method area { 0 }
}
class Circle :isa(Shape) {
field $radius :param :reader;
method area { 3.14159265 * $radius ** 2 }
}
Only methods are inherited. Fields are per-class storage. A subclass that needs the parent’s state asks through a reader:
class Labelled :isa(Shape) {
field $label :param :reader;
method describe { "$label (" . $self->colour . ')' }
}
Multiple inheritance is not supported. :isa(A, B) is a syntax
error, and a class cannot carry more than one :isa attribute.
The inheritance chapter explains why, and covers
the role-based alternative.
Calling up the chain#
SUPER:: still works, exactly as it does for
bless-based classes:
class Dog :isa(Animal) {
method introduce {
$self->SUPER::introduce;
say "and I can fetch";
}
}
__CLASS__#
Inside a field initialiser or a method body, __CLASS__ is the
class currently being constructed. In a subclass, __CLASS__
refers to the subclass even inside an inherited method or an
inherited field initialiser. Use it for factory-style hooks:
class Base {
sub DEFAULT_X { 10 }
field $x = __CLASS__->DEFAULT_X;
method x { $x }
}
class Tuned :isa(Base) {
sub DEFAULT_X { 99 }
}
say Tuned->new->x; # 99
Interoperation with classical OO#
A class-declared class is a regular package. Code that uses
ref, the
isa operator, UNIVERSAL::isa,
and UNIVERSAL::can keeps working:
my $c = Circle->new(radius => 2);
say ref $c; # Circle
say $c isa Shape ? 'yes' : 'no'; # yes
say $c->can('area') ? 'yes' : 'no'; # yes
A classical class can subclass a modern class and vice versa, as
long as the classical side goes through methods (never through
$self->{fieldname}) — see the migration
chapter for the full interop story.
Edge cases worth knowing#
Do not write
sub new. The constructor is generated. A user-writtennewinside aclassbody is rejected at compile time.Treat
@ISAas read-only forclass-declared classes. Runtime manipulation is undefined behaviour.$selfis read-only inside a method. Assigning to$selfis a runtime error.packagecannot reopen a class. Once a namespace has been declared withclass, a laterpackagestatement for the same name is a compile-time error, and vice versa.Statement-form scope.
class Foo;consumes the rest of the enclosing block, typically the rest of the file. A subsequentclassorpackageends the body; there is no way to close a statement-form class early.
Where to go next#
Inheritance and method resolution — the full story of how method lookup works across
:isachains and across mixed classical/modern hierarchies.Roles and delegation — the recommended answer to “I want to share behaviour across unrelated classes.”
Migrating from classical to modern — if you are staring at a
bless-based class and wondering what the equivalentclassblock looks like.