NAME
Object::Pad
- a simple syntax for lexical field-based objects
SYNOPSIS
On perl version 5.26 onwards:
use v5.26;
use Object::Pad;
class Point {
field $x :param = 0;
field $y :param = 0;
method move ($dX, $dY) {
$x += $dX;
$y += $dY;
}
method describe () {
print "A point at ($x, $y)\n";
}
}
Point->new(x => 5, y => 10)->describe;
Or, for older perls that lack signatures:
use Object::Pad;
class Point {
field $x :param = 0;
field $y :param = 0;
method move {
my ($dX, $dY) = @_;
$x += $dX;
$y += $dY;
}
method describe {
print "A point at ($x, $y)\n";
}
}
Point->new(x => 5, y => 10)->describe;
DESCRIPTION
This module provides a simple syntax for creating object classes, which uses private variables that look like lexicals as object member fields.
While most of this module has evolved into a stable state in practice, parts remain experimental because the design is still evolving, and many features and ideas have yet to implemented. I don't yet guarantee I won't have to change existing details in order to continue its development. Feel free to try it out in experimental or newly-developed code, but don't complain if a later version is incompatible with your current code and you'll have to change it.
That all said, please do get in contact if you find the module overall useful. The more feedback you provide in terms of what features you are using, what you find works, and what doesn't, will help the ongoing development and hopefully eventual stability of the design. See the "FEEDBACK" section.
Experimental Features
Since version 0.63.
Some of the features of this module are currently marked as experimental. They will provoke warnings in the experimental
category, unless silenced.
You can silence this with no warnings 'experimental'
but then that will silence every experimental warning, which may hide others unintentionally. For a more fine-grained approach you can instead use the import line for this module to only silence the module's warnings selectively:
use Object::Pad ':experimental(init_expr)';
use Object::Pad ':experimental(mop)';
use Object::Pad ':experimental(custom_field_attr)';
use Object::Pad ':experimental(adjust_params)';
use Object::Pad ':experimental'; # all of the above
Since version 0.64.
Multiple experimental features can be enabled at once by giving multiple names in the parens, separated by spaces:
use Object::Pad ':experimental(init_expr mop)';
Automatic Construction
Classes are automatically provided with a constructor method, called new
, which helps create the object instances. This may respond to passed arguments, automatically assigning values of fields, and invoking other blocks of code provided by the class. It proceeds in the following stages:
The BUILDARGS phase
If the class provides a BUILDARGS
class method, that is used to mangle the list of arguments before the BUILD
blocks are called. Note this must be a class method not an instance method (and so implemented using sub
). It should perform any SUPER
chaining as may be required.
@args = $class->BUILDARGS( @_ )
Field assignment
If any field in the class has the :param
attribute, then the constructor will expect to receive its argmuents in an even-sized list of name/value pairs. This applies even to fields inherited from the parent class or applied roles. It is therefore a good idea to shape the parameters to the constructor in this way in roles, and in classes if you intend your class to be extended.
The constructor will also check for required parameters (these are all the parameters for fields that do not have default initialisation expressions). If any of these are missing an exception is thrown.
The BUILD phase
As part of the construction process, the BUILD
block of every component class will be invoked, passing in the list of arguments the constructor was invoked with. Each class should perform its required setup behaviour, but does not need to chain to the SUPER
class first; this is handled automatically.
The ADJUST phase
Next, the ADJUST
block of every component class is invoked. This happens after the fields are assigned their initial values and the BUILD
blocks have been run.
The strict-checking phase
Finally, before the object is returned, if the ":strict(params)" class attribute is present, then the constructor will throw an exception if there are any remaining named arguments left over after assigning them to fields as per :param
declarations, and running any ADJUST
blocks.
KEYWORDS
class
class Name :ATTRS... {
...
}
class Name :ATTRS...;
Behaves similarly to the package
keyword, but provides a package that defines a new class. Such a class provides an automatic constructor method called new
.
As with package
, an optional block may be provided. If so, the contents of that block define the new class and the preceding package continues afterwards. If not, it sets the class as the package context of following keywords and definitions.
As with package
, an optional version declaration may be given. If so, this sets the value of the package's $VERSION
variable.
class Name VERSION { ... }
class Name VERSION;
A single superclass is supported by the keyword isa
Since version 0.41.
class Name isa BASECLASS {
...
}
class Name isa BASECLASS BASEVER {
...
}
Prior to version 0.41 this was called extends
, but since version 0.73 this is no longer recognised. The isa
keyword is now deprecated, in favour of the ":isa" attribute which is preferred because it follows a more standard grammar without this special-case.
One or more roles can be composed into the class by the keyword does
Since version 0.41.
class Name does ROLE, ROLE,... {
...
}
Prior to version 0.41 this was called implements
, but since version 0.73 this is no longer recognised. The does
keyword is now deprecated, in favour of the ":does" attribute which is preferred because it follows a more standard grammar without this special-case.
An optional list of attributes may be supplied in similar syntax as for subs or lexical variables. (These are annotations about the class itself; the concept should not be confused with per-object-instance data, which here is called "fields").
Whitespace is permitted within the value and is automatically trimmed, but as standard Perl parsing rules, no space is permitted between the attribute's name and the open parenthesis of its value:
:attr( value here ) # is permitted
:attr (value here) # not permitted
The following class attributes are supported:
:isa
:isa(CLASS)
:isa(CLASS CLASSVER)
Since version 0.57.
Declares a superclass that this class extends. At most one superclass is supported.
If the package providing the superclass does not exist, an attempt is made to load it by code equivalent to
require CLASS;
and thus it must either already exist, or be locatable via the usual @INC
mechanisms.
The superclass may or may not itself be implemented by Object::Pad
, but if it is not then see "SUBCLASSING CLASSIC PERL CLASSES" for further detail on the semantics of how this operates.
An optional version check can also be supplied; it performs the equivalent of
BaseClass->VERSION( $ver )
:does
:does(ROLE)
:does(ROLE ROLEVER)
Since version 0.57.
Composes a role into the class; optionally requiring a version check on the role package. This is a newer form of the implements
and does
keywords and should be preferred for new code.
Multiple roles can be composed by using multiple :does
attributes, one per role.
The package will be loaded in a similar way to how the ":isa" attribute is handled.
:repr(TYPE)
Sets the representation type for instances of this class. Must be one of the following values:
:repr(native)
The native representation. This is an opaque representation type whose contents are not specified. It only works for classes whose entire inheritance hierarchy is built only from classes based on Object::Pad
.
:repr(HASH)
The representation will be a blessed hash reference. The instance data will be stored in an array referenced by a key called Object::Pad/slots
, which is fairly unlikely to clash with existing storage on the instance. No other keys will be used; they are available for implementions and subclasses to use. The exact format of the value stored here is not specified and may change between module versions, though it can be relied on to be well-behaved as some kind of perl data structure for purposes of modules like Data::Dumper or serialisation into things like YAML
or JSON
.
This representation type may be useful when converting existing classes into using Object::Pad
where there may be existing subclasses of it that presume a blessed hash for their own use.
:repr(magic)
The representation will use MAGIC to apply the instance data in a way that is invisible at the Perl level, and shouldn't get in the way of other things the instance is doing even in XS modules.
This representation type is the only one that will work for subclassing existing classes that do not use blessed hashes.
:repr(autoselect), :repr(default)
Since version 0.23.
This representation will select one of the representations above depending on what is best for the situation. Classes not derived from a non-Object::Pad
base class will pick native
, and classes derived from non-Object::Pad
bases will pick either the HASH
or magic
forms depending on whether the instance is a blessed hash reference or some other kind.
This achieves the best combination of DWIM while still allowing the common forms of hash reference to be inspected by Data::Dumper
, etc. This is the default representation type, and does not have to be specifically requested.
:strict(params)
Since version 0.43.
Can only be applied to classes that contain no BUILD
blocks. If set, then the constructor will complain about any unrecognised named arguments passed to it (i.e. names that do not correspond to the :param
of any defined field and left unconsumed by any ADJUST
block).
Since BUILD
blocks can inspect the arguments arbitrarily, the presence of any such block means the constructor cannot determine which named arguments are not recognised.
This attribute is a temporary stepping-stone for compatibility with existing code. It is recommended to enable this whenever possible, as a later version of this module will likely perform this behaviour unconditionally whenever no BUILD
blocks are present.
role
role Name :ATTRS... {
...
}
role Name :ATTRS...;
Since version 0.32.
Similar to class
, but provides a package that defines a new role. A role acts similar to a class in some respects, and differently in others.
Like a class, a role can have a version, and named methods.
role Name VERSION {
method a { ... }
method b { ... }
}
A role does not provide a constructor, and instances cannot directly be constructed. A role cannot extend a class.
A role can declare that it requires methods of given names from any class that implements the role.
role Name {
requires METHOD;
}
A role can provide instance fields. These are visible to any ADJUST
blocks or methods provided by that role.
Since version 0.33.
role Name {
field $f;
ADJUST { $f = "a value"; }
method field { return $f; }
}
Since version 0.57 a role can declare that it provides another role:
role Name :does(OTHERROLE) { ... }
role Name :does(OTHERROLE OTHERVER) { ... }
This will include all of the methods from the included role. Effectively this means that applying the "outer" role to a class will imply applying the other role as well.
The following role attributes are supported:
:compat(invokable)
Since version 0.35.
Enables a form of backward-compatibility behaviour useful for gradually upgrading existing code from classical Perl inheritance or mixins into using roles.
Normally, methods of a role cannot be directly invoked and the role must be applied to an Object::Pad-based class in order to be used. This however presents a problem when gradually upgrading existing code that already uses techniques like roles, multiple inheritance or mixins when that code may be split across multiple distributions, or for some other reason cannot be upgraded all at once. Methods within a role that has the :compat(invokable)
attribute applied to it may be directly invoked on any object instance. This allows the creation of a role that can still provide code for existing classes written in classical Perl that has not yet been rewritten to use Object::Pad
.
The tradeoff is that a :compat(invokable)
role may not create field data using the "field" keyword. Whatever behaviours the role wishes to perform must be provided only by calling other methods on $self
, or perhaps by making assumptions about the representation type of instances.
It should be stressed again: This option is only intended for gradual upgrade of existing classical Perl code into using Object::Pad
. When all existing code is using Object::Pad
then this attribute can be removed from the role.
field
field $var;
field @var;
field %var;
field $var :ATTR ATTR...;
field $var = EXPR;
field $var { BLOCK }
Since version 0.66.
Declares that the instances of the class or role have a member field of the given name. This member field will be accessible as a lexical variable within any method
declarations in the class.
Array and hash members are permitted and behave as expected; you do not need to store references to anonymous arrays or hashes.
Member fields are private to a class or role. They are not visible to users of the class, nor inherited by subclasses nor any class that a role is applied to. In order to provide access to them a class may wish to use "method" to create an accessor, or use the attributes such as ":reader" to get one generated.
The following field attributes are supported:
:reader, :reader(NAME)
Since version 0.27.
Generates a reader method to return the current value of the field. If no name is given, the name of the field is used. A single prefix character _
will be removed if present.
field $x :reader;
# equivalent to
field $x; method x { return $x }
Since version 0.55 these are permitted on any field type, but prior versions only allowed them on scalar fields. The reader method behaves identically to how a lexical variable would behave in the same context; namely returning a list of values from an array or key/value pairs from a hash when in list context, or the number of items or keys when in scalar context.
field @items :reader;
foreach my $item ( $obj->items ) { ... } # iterates the list of items
my $count = $obj->items; # yields count of items
:writer, :writer(NAME)
Since version 0.27.
Generates a writer method to set a new value of the field from its arguments. If no name is given, the name of the field is used prefixed by set_
. A single prefix character _
will be removed if present.
field $x :writer;
# equivalent to
field $x; method set_x { $x = shift; return $self }
Since version 0.28 a generated writer method will return the object invocant itself, allowing a chaining style.
$obj->set_x("x")
->set_y("y")
->set_z("z");
Since version 0.55 these are permitted on any field type, but prior versions only allowed them on scalar fields. On arrays or hashes, the writer method takes a list of values to be assigned into the field, completely replacing any values previously there.
:mutator, :mutator(NAME)
Since version 0.27.
Generates an lvalue mutator method to return or set the value of the field. These are only permitted for scalar fields. If no name is given, the name of the field is used. A single prefix character _
will be removed if present.
field $x :mutator;
# equivalent to
field $x; method x :lvalue { $x }
Since version 0.28 all of these generated accessor methods will include argument checking similar to that used by subroutine signatures, to ensure the correct number of arguments are passed - usually zero, but exactly one in the case of a :writer
method.
:accessor, :accessor(NAME)
Since version 0.53.
Generates a combined reader-writer accessor method to set or return the value of the field. These are only permitted for scalar fields. If no name is given, the name of the field is used. A prefix character _
will be removed if present.
This method takes either zero or one additional arguments. If an argument is passed, the value of the field is set from this argument (even if it is undef
). If no argument is passed (i.e. scalar @_
is false) then the field is not modified. In either case, the value of the field is then returned.
field $x :accessor;
# equivalent to
field $x;
method field {
$x = shift if @_;
return $x;
}
:weak
Since version 0.44.
Generated code which sets the value of this field will weaken it if it contains a reference. This applies to within the constructor if :param
is given, and to a :writer
accessor method. Note that this only applies to automatically generated code; not normal code written in regular method bodies. If you assign into the field variable you must remember to call Scalar::Util::weaken
(or builtin::weaken
on Perl 5.36 or above) yourself.
:param, :param(NAME)
Since version 0.41.
Sets this field to be initialised automatically in the generated constructor. This is only permitted on scalar fields. If no name is given, the name of the field is used. A single prefix character _
will be removed if present.
Any field that has :param
but does not have a default initialisation expression or block becomes a required argument to the constructor. Attempting to invoke the constructor without a named argument for this will throw an exception. In order to make a parameter optional, make sure to give it a default expression - even if that expression is undef
:
field $x :param; # this is required
field $z :param = undef; # this is optional
Any field that has a :param
and an initialisation block will only run the code in the block if required by the constructor. If a named parameter is passed to the constructor for this field, then its code block will not be executed.
Values for fields are assigned by the constructor before any BUILD
blocks are invoked.
Field Initialiser Expressions
Since version 0.54 a deferred statement block is also permitted, on any field variable type. This permits code to be executed as part of the instance constructor, rather than running just once when the class is set up. Code in a field initialisation block is roughly equivalent to being placed in a BUILD
or ADJUST
block.
Since version 0.73 this may also be written as a plain expression introduced by an equals symbol (=
). This is equivalent to using a block. Note carefully: the equals symbol is part of the field
syntax; it is not simply a runtime assignment operator that happens once at the time the class is declared. Just like the block form describe above, the expression is evaluated during the constructor of every instance.
Note that $self
is specifically not visible during an initialiser expression. This is because the object is not yet fully constructed, so it would be dangerous to allow access to it while in this state. However, the __CLASS__
keyword is available, so initialiser expressions can make use of class-based dispatch to invoke class-level methods to help provide values.
This feature should be considered experimental, and will emit warnings to that effect. They can be silenced with
use Object::Pad qw( :experimental(init_expr) );
Control flow that attempts to leave a field initialiser expression or block is not permitted. This includes any return
expression, any next/last/redo
outside of a loop, with a dynamically-calculated label expression, or with a label that it doesn't appear in. goto
statements are also currently forbidden, though known-safe ones may be permitted in future.
Loop control expressions that are known at compiletime to affect a loop that they appear within are permitted.
field $x { foreach(@list) { next; } } # this is fine
field $x { LOOP: while(1) { last LOOP; } } # this is fine too
has
has $var;
has @var;
has %var;
has $var = EXPR;
has $var { BLOCK }
An older version of the "field" keyword.
This generally behaves like field
, except that inline expressions are evaluated immediately, once, during class declaration time. These are not stored to be evaluated for each constructor.
Because of the one-shot immediate nature of these initialisation expressions (and a bunch of other reasons), the has
keyword is now discouraged for use. Use the field
keyword instead.
If you need to evaluate an expression exactly once during the class declaration and assign its now-constant value to every instace, store it in a regular my
variable instead:
my $default_var = EXPR;
field $var = $default_var;
method
method NAME {
...
}
method NAME (SIGNATURE) {
...
}
method NAME :ATTRS... {
...
}
method NAME;
Declares a new named method. This behaves similarly to the sub
keyword, except that within the body of the method all of the member fields are also accessible. In addition, the method body will have a lexical called $self
which contains the invocant object directly; it will already have been shifted from the @_
array.
If the method has no body and is given simply as a name, this declares a required method for a role. Such a method must be provided by any class that implements the role. It will be a compiletime error to combine the role with a class that does not provide this.
The signatures
feature is automatically enabled for method declarations. In this case the signature does not have to account for the invocant instance; that is handled directly.
method m ($one, $two) {
say "$self invokes method on one=$one two=$two";
}
...
$obj->m(1, 2);
A list of attributes may be supplied as for sub
. The most useful of these is :lvalue
, allowing easy creation of read-write accessors for fields (but see also the :reader
, :writer
and :mutator
field attributes).
class Counter {
field $count;
method count :lvalue { $count }
}
my $c = Counter->new;
$c->count++;
Every method automatically gets the :method
attribute applied, which suppresses warnings about ambiguous calls resolved to core functions if the name of a method matches a core function.
The following additional attributes are recognised by Object::Pad
directly:
:override
Since version 0.29.
Marks that this method expects to override another of the same name from a superclass. It is an error at compiletime if the superclass does not provide such a method.
:common
Since version 0.62.
Marks that this method is a class-common method, instead of a regular instance method. A class-common method may be invoked on class names instead of instances. Within the method body there is a lexical $class
available, rather than $self
. Because it is not associated with a particular object instance, a class-common method cannot see instance fields.
method (lexical)
method $var { ... }
method $var :ATTRS... (SIGNATURE) { ... }
Since version 0.59.
Declares a new lexical method. Lexical methods are not visible via the package namespace, but instead are stored directly in a lexical variable (with the same scoping rules as regular my
variables). These can be invoked by subsequent method code in the same block by using $self->$var(...)
method call syntax.
class WithPrivate {
field $var;
# Lexical methods can still see instance fields as normal
method $inc_var { $var++; say "Var was incremented"; }
method $dec_var { $var--; say "Var was decremented"; }
method bump {
$self->$inc_var;
say "In the middle";
$self->$dec_var;
}
}
my $obj = WithPrivate->new;
$obj->bump;
# Neither $inc_var nor $dec_var are visible here
This effectively provides the ability to define private methods, as they are inaccessible from outside the block that defines the class. In addition, there is no chance of a name collision because lexical variables in different scopes are independent, even if they share the same name. This is particularly useful in roles, to create internal helper methods without letting those methods be visible to callers, or risking their names colliding with other named methods defined on the consuming class.
BUILD
BUILD {
...
}
BUILD (SIGNATURE) {
...
}
Since version 0.27.
Declares the builder block for this component class. A builder block may use subroutine signature syntax, as for methods, to assist in unpacking its arguments. A build block is not a subroutine and thus is not permitted to use subroutine attributes (for example :lvalue
).
Note that a BUILD
block is a named phaser block and not a method. Attempts to create a method named BUILD
(i.e. with syntax method BUILD {...}
) will fail with a compiletime error, to avoid this confusion.
ADJUST
ADJUST {
...
}
Since version 0.43.
Declares an adjust block for this component class. This block of code runs within the constructor, after any BUILD
blocks and automatic field value assignment. It can make any final adjustments to the instance (such as initialising fields from calculated values).
An adjust block is not a subroutine and thus is not permitted to use subroutine attributes (except see below). Note that an ADJUST
block is a named phaser block and not a method; it does not use the sub
or method
keyword.
Currently, an ADJUST
block receives a reference to the hash containing the current constructor arguments, as per "ADJUSTPARAMS" (see below). This was added in version 0.66 but will be removed again as it conflicts with the more flexible and generally nicer named-parameter ADJUST :params
syntax (see below). Such uses should be considered deprecated. A warning will be printed to indicate this whenever an ADJUST
block uses a signature. This warning can be quieted by using ADJUSTPARAMS
instead. Additionally, a warning may be printed on code that attempts to access the params hashref via the @_
array.
ADJUST :params
ADJUST :params ( :$var1, :$var2, ... ) {
...
}
ADJUST :params ( :$var1, :$var2, ..., %varN ) {
...
}
Since version 0.70.
An ADJUST
block can marked with a :params
attribute, meaning that it consumes additional constructor parameters by assigning them into lexical variables.
This feature should be considered experimental, and will emit warnings to that effect. They can be silenced with
use Object::Pad qw( :experimental(adjust_params) );
Before the block itself, a list of lexical variables are introduced, inside parentheses. The name of each one is preceeded by a colon, and consumes a constructor parameter of the same name. These parameters are considered "consumed" for the purposes of a :strict(params)
check.
A named parameter may be provided with default expression, which is evaluated if no matching named argument is provided to the constructor. As with fields, if a named parameter has no defaulting expression it becomes a required argument to the constructor; an exception is thrown by the constructor if it absent.
For example,
ADJUST :params ( :$x, :$y = "default", :$z ) { ... }
Note here that x
and z
are required parameters for the constructor of a class containing this block, but y
is an optional parameter whose value will be filled in by the expression if not provided. Because these parameters are named and not positional, there is no ordering constraint; required and optional parameters can be freely mixed.
Optional parameters can also use the //=
and ||=
operators to provide a default expression. In these cases, the default will be applied if the caller did not provide the named argument at all, or if the provided value was not defined (for //=
) or not true (for ||=
).
ADJUST :params ( :$name //= "unnamed" ) { ... }
Like with subroutine signature parameters, every declared named parameter is visible to the defaulting expression of all the later ones. This permits values to be calculated based on other ones. For example,
ADJUST :params ( :$thing = undef, :$things = [ $thing ] ) {
# Here, @$things is a list of values
}
This permits the caller to pass a list of values via an array reference in the things
parameter, or a single value in thing
.
The final element may be a regular hash variable. This requests that all remaining named parameters are made available inside it. The code in the block should delete
from this hash any parameters it wishes to consume, as with the earlier case above.
It is unspecified whether named fields or parameters for subclasses yet to be processed are visible to hashes of earlier superclasses. In the current implementation they are, but code should not rely on this fact.
Note also that there must be a space between the :params
attribute and the parentheses holding the named parameters. If this space is not present, perl will parse the parentheses as if they are the value to the :params()
attribute, and this will fail to parse as intended. As with other attributes and subroutine signatures, this whitespace is significant.
(This notation is borrowed from a plan to add named parameter support to perl's subroutine signature syntax).
ADJUSTPARAMS
Since version 0.51.
ADJUSTPARAMS ( $params ) { # on perl 5.26 onwards
...
}
ADJUST {
my $params = shift;
...
}
A variant of an ADJUST
block that receives a reference to the hash containing the current constructor parameters. This hash will not contain any constructor parameters already consumed by ":param" declarations on any fields, but only the leftovers once those are processed.
The code in the block should delete
from this hash any parameters it wishes to consume. Once all the ADJUST
blocks have run, any remaining keys in the hash will be considered errors, subject to the ":strict(params)" check.
__CLASS__
my $classname = __CLASS__;
Since version 0.72.
Only valid within the body (or signature) of a method
, an ADJUST
block, or the initialising expression of a field
. Yields the class name of the instance that the method, block or expression is invoked on.
This is similar to the core perl __PACKAGE__
constant, except that it cares about the dynamic class of the actual instance, not the static class the code belongs to. When invoked by a subclass instance that inherited code from its superclass it yields the name of the class of the instance regardless of which class defined the code.
For example,
class BaseClass {
ADJUST { say "Constructing an instance of " . __CLASS__; }
}
class DerivedClass :isa(BaseClass) { }
my $obj = DerivedClass->new;
Will produce the following output
Constructing an instance of DerivedClass
This is particularly useful in field initialisers for invoking (constant) methods on the invoking class to provide default values for fields. This way a subclass could provide a different value.
class Timer {
use constant DEFAULT_DURATION => 60;
field $duration = __CLASS__->DEFAULT_DURATION;
}
class ThreeMinuteTimer :isa(Timer) {
use constant DEFAULT_DURATION => 3 * 60;
}
requires
requires NAME;
Declares that this role requires a method of the given name from any class that implements it. It is an error at compiletime if the implementing class does not provide such a method.
This form of declaring a required method is now vaguely discouraged, in favour of the bodyless method
form described above.
CREPT FEATURES
While not strictly part of being an object system, this module has nevertheless gained a number of behaviours by feature creep, as they have been found useful.
Implied Pragmata
In order to encourage users to write clean, modern code, the body of the class
block acts as if the following pragmata are in effect:
use strict;
use warnings;
no indirect ':fatal'; # or no feature 'indirect' on perl 5.32 onwards
use feature 'signatures';
This list may be extended in subsequent versions to add further restrictions and should not be considered exhaustive.
Further additions will only be ones that remove "discouraged" or deprecated language features with the overall goal of enforcing a more clean modern style within the body. As long as you write code that is in a clean, modern style (and I fully accept that this wording is vague and subjective) you should not find any new restrictions to be majorly problematic. Either the code will continue to run unaffected, or you may have to make some small alterations to bring it into a conforming style.
Yield True
A class
statement or block will yield a true boolean value. This means that it can be used directly inside a .pm file, avoiding the need to explicitly yield a true value from the end of it.
SUBCLASSING CLASSIC PERL CLASSES
There are a number of details specific to the case of deriving an Object::Pad
class from an existing classic Perl class that is not implemented using Object::Pad
.
Storage of Instance Data
Instances will pick either the :repr(HASH)
or :repr(magic)
storage type.
Object State During Methods Invoked By Superclass Constructor
It is common in classic Perl OO style to invoke methods on $self
during the constructor. This is supported here since Object::Pad
version 0.19. Note however that any methods invoked by the superclass constructor may not see the object in a fully consistent state. (This fact is not specific to using Object::Pad
and would happen in classic Perl OO as well). The field initialisers will have been invoked but the BUILD
and ADJUST
blocks will not.
For example; in the following
package ClassicPerlBaseClass {
sub new {
my $self = bless {}, shift;
say "Value seen by superconstructor is ", $self->get_value;
return $self;
}
sub get_value { return "A" }
}
class DerivedClass :isa(ClassicPerlBaseClass) {
field $_value = "B";
ADJUST {
$_value = "C";
}
method get_value { return $_value }
}
my $obj = DerivedClass->new;
say "Value seen by user is ", $obj->get_value;
Until the ClassicPerlBaseClass::new
superconstructor has returned the ADJUST
block will not have been invoked. The $_value
field will still exist, but its value will be B
during the superconstructor. After the superconstructor, the BUILD
and ADJUST
blocks are invoked before the completed object is returned to the user. The result will therefore be:
Value seen by superconstructor is B
Value seen by user is C
STYLE SUGGESTIONS
While in no way required, the following suggestions of code style should be noted in order to establish a set of best practices, and encourage consistency of code which uses this module.
$VERSION declaration
While it would be nice for CPAN and other toolchain modules to parse the embedded version declarations in class
statements, the current state at time of writing (June 2020) is that none of them actually do. As such, it will still be necessary to make a once-per-file $VERSION
declaration in syntax those modules can parse.
Further note that these modules will also not parse the class
declaration, so you will have to duplicate this with a package
declaration as well as a class
keyword. This does involve repeating the package name, so is slightly undesirable.
It is hoped that eventually upstream toolchain modules will be adapted to accept the class
syntax as being sufficient to declare a package and set its version.
See also
File Layout
Begin the file with a use Object::Pad
line; ideally including a minimum-required version. This should be followed by the toplevel package
and class
declarations for the file. As it is at toplevel there is no need to use the block notation; it can be a unit class.
There is no need to use strict
or apply other usual pragmata; these will be implied by the class
keyword.
use Object::Pad 0.16;
package My::Classname 1.23;
class My::Classname;
# other use statements
# field, methods, etc.. can go here
Field Names
Field names should follow similar rules to regular lexical variables in code - lowercase, name components separated by underscores. For tiny examples such as "dumb record" structures this may be sufficient.
class Tag {
field $name :mutator;
field $value :mutator;
}
In larger examples with lots of non-trivial method bodies, it can get confusing to remember where the field variables come from (because we no longer have the $self->{ ... }
visual clue). In these cases it is suggested to prefix the field names with a leading underscore, to make them more visually distinct.
class Spudger {
field $_grapefruit;
...
method mangle {
$_grapefruit->peel; # The leading underscore reminds us this is a field
}
}
WITH OTHER MODULES
Syntax::Keyword::Dynamically
A cross-module integration test asserts that dynamically
works correctly on object instance fields:
use Object::Pad;
use Syntax::Keyword::Dynamically;
class Container {
field $value = 1;
method example {
dynamically $value = 2;
,..
# value is restored to 1 on return from this method
}
}
Future::AsyncAwait
As of Future::AsyncAwait version 0.38 and Object::Pad version 0.15, both modules now use XS::Parse::Sublike to parse blocks of code. Because of this the two modules can operate together and allow class methods to be written as async subs which await expressions:
use Future::AsyncAwait;
use Object::Pad;
class Example
{
async method perform ($block)
{
say "$self is performing code";
await $block->();
say "code finished";
}
}
These three modules combine; there is additionally a cross-module test to ensure that object instance fields can be dynamically
set during a suspended async method
.
Devel::MAT
When using Devel::MAT to help analyse or debug memory issues with programs that use Object::Pad
, you will likely want to additionally install the module Devel::MAT::Tool::Object::Pad. This will provide new commands and extend existing ones to better assist with analysing details related to Object::Pad
classes and instances of them.
pmat> fields 0x55d7c173d4b8
The field AV ARRAY(3)=NativeClass at 0x55d7c173d4b8
Ix Field Value
0 $sfield SCALAR(UV) at 0x55d7c173d938 = 123
...
pmat> identify 0x55d7c17606d8
REF() at 0x55d7c17606d8 is:
└─the %hfield field of ARRAY(3)=NativeClass at 0x55d7c173d4b8, which is:
...
DESIGN TODOs
The following points are details about the design of pad field-based object systems in general:
Is multiple inheritance actually required, if role composition is implemented including giving roles the ability to use private fields?
Consider the visibility of superclass fields to subclasses. Do subclasses even need to be able to see their superclass's fields, or are accessor methods always appropriate?
Concrete example: The
$self->{split_at}
access that Tickit::Widget::HSplit makes of its parent class Tickit::Widget::LinearSplit.
IMPLEMENTATION TODOs
These points are more about this particular module's implementation:
Consider multiple inheritance of subclassing, if that is still considered useful after adding roles.
Work out why
no indirect
doesn't appear to work properly before perl 5.20.Work out why we don't get a
Subroutine new redefined at ...
warning if wesub new { ... }
The
local
modifier does not work on field variables, because they appear to be regular lexicals to the parser at that point. A workaround is to use Syntax::Keyword::Dynamically instead:use Syntax::Keyword::Dynamically; field $loglevel; method quietly { dynamically $loglevel = LOG_ERROR; ... }
FEEDBACK
The following resources are useful forms of providing feedback, especially in the form of reports of what you find good or bad about the module, requests for new features, questions on best practice, etc...
The RT queue at https://rt.cpan.org/Dist/Display.html?Name=Object-Pad.
The
#cor
IRC channel onirc.perl.org
.
SPONSORS
With thanks to the following sponsors, who have helped me be able to spend time working on this module and other perl features.
Oetiker+Partner AG https://www.oetiker.ch/en/
Deriv http://deriv.com
Perl-Verein Schweiz https://www.perl-workshop.ch/
Additional details may be found at https://github.com/Ovid/Cor/wiki/Sponsors.
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>