NAME
Object::Pad::Keyword::Accessor
- declare lvalue accessors on Object::Pad
classes
SYNOPSIS
use Object::Pad;
use Object::Pad::Keyword::Accessor;
class Counter {
use Carp;
field $count = 0;
accessor count {
get { return $count }
set ($new) {
$new =~ m/^\d+$/ or croak "Invalid new value for count";
$count = $new;
say "Count has been updated to $count";
}
}
}
my $c = Counter->new;
$c->count = 20;
$c->count = "hello"; # is not permitted
DESCRIPTION
This module provides a new keyword for declaring accessor methods that behave as lvalues as members of Object::Pad-based classes.
While Object::Pad
does permit fields of classes to be exposed to callers via lvalue mutator methods by using the :mutator
field attribute, these are generally not that useful in real cases. Fields exposed using this technique have no validation, and cannot trigger any other code to be executed after update.
The "accessor" keyword provided by this module offers an alternative. The lvalue accessor methods it provides into the class fully support running arbitrary code on read and write access, permitting any kind of check or triggering action. In fact, the accessor does not even need to be directly backed by a field at all. The accessor permits the class to specify its interface by which other code will interact with it, without being forced into any particular implementation of that interface.
This module is a very early proof-of-concept, both of the syntax itself and the underlying ability of Object::Pad
to support such syntax extensions as a third-party module.
Motivation
At first glance it may not seem immediately obvious why you would want to do this. After all, these accessors do not permit any new behaviours that couldn't be performed with a more traditional pair of get_*
+ set_*
methods.
The first reason is simply the declaration of intent on behalf of the class. Given a similarly-named get
/set
pair of methods, a user could guess that they probably behave like an accessor. But by providing the behaviour as a real accessor this makes a much firmer statement; where the user can much more strongly expect things like multiple read accesses to be idempotent and yield the same value as the most recent write access.
The second reason is that perl already provides quite a number of mutating operators that allow a value to be edited in-place. These would not work at all with a get
/set
method pair, whereas they work just fine with these accessors. For example, given the code in the synopsis, the counter could be incremented simply by
$c->count++;
Whereas, if an lvalue accessor did not exist you would have to write this as something like
$c->set_count( $c->count + 1 );
The earlier form is much simpler, shorter, and much more obvious at first glance what it's doing. You don't, for example, have to check that the get
and set
method pair are indeed operating on the same thing. There's just one accessor of one object.
KEYWORDS
accessor
accessor NAME { PARTS... }
Declares a new accessor method of the given name into the class. This will appear as a regular method, much as if declared by code such as
method NAME :lvalue { ... }
The behaviour of the accessor will be controlled by the parts given in the braces following its name. Note that these braces are not simply a code block; it does not accept arbitrary perl code. Only the following keywords may be used there.
get
accessor NAME { get { CODE... } }
Provides a body of code to be invoked when a caller is attempting to read the value of the accessor. The code block behaves as a method, having access to the $self
lexical as well as any fields already defined on the class. The value it returns will be the value passed back to the caller who read the accessor.
set
accessor NAME { set { CODE... } }
accessor NAME { set ($var) { CODE... } }
Provides a body of code to be invoked when a caller is attempting to write a new value for the accessor. The code block behaves like a method, having access to the $self
lexical as well as any fields already defined on the class. The new value written by the caller will appear as the first positional argument to the method, accessible by code such as shift
or $_[0]
.
A second more succinct form allows the block to be written with a prefixed declaration of a variable name, using the same syntax as a subroutine signature (though this is not implemented by the same mechanism; it works on perl versions older than signatures are supported, and does not allow a defaulting expression or other syntax).
If the code in this block throws an exception, that will propagate up to the caller who attempted to write the value. If this happens before the code block has stored the new value somewhere as a side-effect, then this will have the appearance of denying the modification at all. This is the way in which a validation check can be implemented.
Similarly, the code is free to perform any other activity after it has stored the new value. This is is the way that post-update code triggering can be implemented. For example, if the object represents some sort of UI display widget it might decide to redraw the screen to reflect the updated value of whatever field just changed.
TODO
Some syntax to allow a field to be associated with the accessor, so it can automatically generate the
get
code, and assist theset
code. This would permit an alternative form where code blocks for value validation check and post-update trigger were specified instead.Perhaps:
field $x; accessor x { field($x) check ($new) { $new >= 0 or croak "Must be non-negative"; } trigger { $self->updated; } }
Integration with the constructor to permit a named-parameter at construction time that would automatically set the value of the accessor, as if the user had done so manually.
Perhaps:
accessor x :param { ... }
Consider whether to permit the accessor method itself to take arguments, allowing for some kind of indexed or parametric accessor.
Perhaps:
field @palette; accessor colour($index) { get { return $palette[$index]; } set ($new) { $palette[$index] = $new; } }
AUTHOR
Paul Evans <leonerd@leonerd.org.uk>