NAME

Sub::Abstract - Abstract (virtual) methods for plain-Perl OO

VERSION

Version 0.01

SYNOPSIS

package Animal;
use Sub::Abstract;

# Attribute form (stub body required for Attribute::Handlers)
sub speak :Abstract { }
sub eat   :Abstract { }

# Declarative form (no stub body needed)
use Sub::Abstract qw(speak eat);

package Dog;
our @ISA = ('Animal');
sub speak { 'Woof' }    # satisfies the contract; wrapper never fires
# forgot eat -- runtime croak when called

DESCRIPTION

Enforces abstract (virtual) method contracts for plain-Perl OO without requiring Moose or Moo. A subroutine decorated with :Abstract (or named in use Sub::Abstract qw(...)) is replaced at CHECK time with a wrapper that Carp::croaks whenever it is reached.

Perl's MRO ensures the wrapper is only reached when no subclass in the call chain has provided an implementation: if Dog::speak exists, the wrapper installed in Animal::speak is never called.

This module is only meaningful for plain-Perl OO or packages that do not use a full object framework. Moo and Moose handle abstract/required methods in their own object systems.

Two usage forms

Bypass for testing

Either condition alone (OR logic) suppresses the croak:

The HARNESS_ACTIVE bypass can be disabled:

$Sub::Abstract::config{harness_bypass} = 0;

Error message format

speak() is an abstract method of Animal and must be implemented by Dog

PUBLIC INTERFACE

import

use Sub::Abstract;                   # attribute form -- no arguments
use Sub::Abstract qw(speak eat);    # declarative form

Purpose

With no arguments: makes the :Abstract attribute globally available.

With one or more method names: installs abstract-croak wrappers for those methods in the calling package at CHECK time (or immediately if CHECK has already fired).

Arguments

Returns

The class name ('Sub::Abstract') as a plain string.

MESSAGES

Message                                              Meaning
---------------------------------------------------  -----------------------------------------------
"Sub::Abstract->import: 'NAME' is not a valid        A name failed the identifier regex.
 Perl identifier"

KNOWN LIMITATIONS

DEPENDENCIES

Carp (core), Attribute::Handlers (core since 5.8), Readonly, Params::Validate::Strict, Return::Set.

SEE ALSO

PUBLIC VARIABLES

$BYPASS

Set to a true value to disable the abstract-method croak for all wrapped subs. Use local in tests:

local $Sub::Abstract::BYPASS = 1;

%config

FORMAL SPECIFICATION

The following Z-notation schemas formally specify the AbstractCroak operation.

-- Type abbreviations
Package  == seq CHAR     -- a non-empty Perl package name string
SubName  == seq CHAR     -- a Perl identifier string

-- System state
+-Registry-------------------------------------------+
| abstract  : P (Package x SubName)                  |
| bypass    : BOOL                                   |
| config    : { harness_bypass : BOOL }              |
+----------------------------------------------------+

-- Initial state
+-InitRegistry---------------------------------------+
| Registry                                           |
|----------------------------------------------------|
| abstract  = {}                                     |
| bypass    = false                                  |
| config    = { harness_bypass |-> true }            |
+----------------------------------------------------+

-- Bypass predicate
bypass_active(R) <=>
    R.bypass or (R.config.harness_bypass and HARNESS_ACTIVE)

-- AbstractCroak: fires when the wrapper is reached (no override in MRO)
+-AbstractCroak--------------------------------------+
| Xi-Registry                                        |
| invocant? : Package                                |
| owner?    : Package                                |
| name?     : SubName                                |
|----------------------------------------------------|
| (owner?, name?) in abstract                        |
| not bypass_active =>                               |
|   croak("name?()" ++ " is an abstract method of " |
|          ++ owner? ++ " and must be implemented by"|
|          ++ invocant?)                             |
+----------------------------------------------------+

-- Key difference from Sub::Private / Sub::Protected:
--   No caller check is performed.  The wrapper always croaks
--   because reaching it means no subclass provided an implementation.

AUTHOR

Nigel Horne, <njh at nigelhorne.com>

LICENCE AND COPYRIGHT

Copyright 2026 Nigel Horne.

Usage is subject to the GPL2 licence terms. If you use it, please let me know.