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 the field declarations. A user-written sub new inside a class body is a compile-time error on purpose — the generated one knows how to wire up :param, run initialisers, and trigger ADJUST blocks 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 method keyword takes care of $self. You never write my $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 passed undef.

  • ||= — 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 our a field, you cannot local one, 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 $self is bound. Inside field $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 an ADJUST block.

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-written new inside a class body is rejected at compile time.

  • Treat @ISA as read-only for class-declared classes. Runtime manipulation is undefined behaviour.

  • $self is read-only inside a method. Assigning to $self is a runtime error.

  • package cannot reopen a class. Once a namespace has been declared with class, a later package statement 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 subsequent class or package ends the body; there is no way to close a statement-form class early.

Where to go next#