File Coverage

blib/lib/Object/Pad/Keyword/Accessor.pm
Criterion Covered Total %
statement 10 10 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 15 15 100.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2022-2023 -- leonerd@leonerd.org.uk
5              
6             package Object::Pad::Keyword::Accessor 0.02;
7              
8 2     2   257669 use v5.14;
  2         18  
9 2     2   22 use warnings;
  2         4  
  2         54  
10              
11 2     2   667 use Object::Pad;
  2         11013  
  2         10  
12              
13             require XSLoader;
14             XSLoader::load( __PACKAGE__, our $VERSION );
15              
16             =head1 NAME
17              
18             C - declare lvalue accessors on C classes
19              
20             =head1 SYNOPSIS
21              
22             use Object::Pad;
23             use Object::Pad::Keyword::Accessor;
24              
25             class Counter {
26             use Carp;
27              
28             field $count = 0;
29              
30             accessor count {
31             get { return $count }
32             set ($new) {
33             $new =~ m/^\d+$/ or croak "Invalid new value for count";
34              
35             $count = $new;
36              
37             say "Count has been updated to $count";
38             }
39             }
40             }
41              
42             my $c = Counter->new;
43             $c->count = 20;
44              
45             $c->count = "hello"; # is not permitted
46              
47             =head1 DESCRIPTION
48              
49             This module provides a new keyword for declaring accessor methods that behave
50             as lvalues as members of L-based classes.
51              
52             While C does permit fields of classes to be exposed to callers
53             via lvalue mutator methods by using the C<:mutator> field attribute, these are
54             generally not that useful in real cases. Fields exposed using this technique
55             have no validation, and cannot trigger any other code to be executed after
56             update.
57              
58             The L keyword provided by this module offers an alternative. The
59             lvalue accessor methods it provides into the class fully support running
60             arbitrary code on read and write access, permitting any kind of check or
61             triggering action. In fact, the accessor does not even need to be directly
62             backed by a field at all. The accessor permits the class to specify its
63             interface by which other code will interact with it, without being forced into
64             any particular implementation of that interface.
65              
66             This module is a very early proof-of-concept, both of the syntax itself and
67             the underlying ability of C to support such syntax extensions as
68             a third-party module.
69              
70             =head2 Motivation
71              
72             At first glance it may not seem immediately obvious why you would want to do
73             this. After all, these accessors do not permit any new behaviours that
74             couldn't be performed with a more traditional pair of C + C
75             methods.
76              
77             The first reason is simply the declaration of intent on behalf of the class.
78             Given a similarly-named C/C pair of methods, a user could guess that
79             they probably behave like an accessor. But by providing the behaviour as a
80             real accessor this makes a much firmer statement; where the user can much more
81             strongly expect things like multiple read accesses to be idempotent and yield
82             the same value as the most recent write access.
83              
84             The second reason is that perl already provides quite a number of mutating
85             operators that allow a value to be edited in-place. These would not work at
86             all with a C/C method pair, whereas they work just fine with these
87             accessors. For example, given the code in the synopsis, the counter could be
88             incremented simply by
89              
90             $c->count++;
91              
92             Whereas, if an lvalue accessor did not exist you would have to write this as
93             something like
94              
95             $c->set_count( $c->count + 1 );
96              
97             The earlier form is much simpler, shorter, and much more obvious at first
98             glance what it's doing. You don't, for example, have to check that the C
99             and C method pair are indeed operating on the same thing. There's just
100             one accessor of one object.
101              
102             =head1 KEYWORDS
103              
104             =head2 accessor
105              
106             accessor NAME { PARTS... }
107              
108             Declares a new accessor method of the given name into the class. This will
109             appear as a regular method, much as if declared by code such as
110              
111             method NAME :lvalue { ... }
112              
113             The behaviour of the accessor will be controlled by the parts given in the
114             braces following its name. Note that these braces are not simply a code block;
115             it does not accept arbitrary perl code. Only the following keywords may be
116             used there.
117              
118             =head3 get
119              
120             accessor NAME { get { CODE... } }
121              
122             Provides a body of code to be invoked when a caller is attempting to read the
123             value of the accessor. The code block behaves as a method, having access to
124             the C<$self> lexical as well as any fields already defined on the class. The
125             value it returns will be the value passed back to the caller who read the
126             accessor.
127              
128             =head3 set
129              
130             accessor NAME { set { CODE... } }
131              
132             accessor NAME { set ($var) { CODE... } }
133              
134             Provides a body of code to be invoked when a caller is attempting to write a
135             new value for the accessor. The code block behaves like a method, having
136             access to the C<$self> lexical as well as any fields already defined on the
137             class. The new value written by the caller will appear as the first positional
138             argument to the method, accessible by code such as C or C<$_[0]>.
139              
140             A second more succinct form allows the block to be written with a prefixed
141             declaration of a variable name, using the same syntax as a subroutine
142             signature (though this is not implemented by the same mechanism; it works on
143             perl versions older than signatures are supported, and does not allow a
144             defaulting expression or other syntax).
145              
146             If the code in this block throws an exception, that will propagate up to the
147             caller who attempted to write the value. If this happens before the code block
148             has stored the new value somewhere as a side-effect, then this will have the
149             appearance of denying the modification at all. This is the way in which a
150             validation check can be implemented.
151              
152             Similarly, the code is free to perform any other activity after it has stored
153             the new value. This is is the way that post-update code triggering can be
154             implemented. For example, if the object represents some sort of UI display
155             widget it might decide to redraw the screen to reflect the updated value of
156             whatever field just changed.
157              
158             =cut
159              
160             sub import
161             {
162 1     1   95 $^H{"Object::Pad::Keyword::Accessor"}++;
163             }
164              
165             sub unimport
166             {
167 1     1   2895 delete $^H{"Object::Pad::Keyword::Accessor"};
168             }
169              
170             =head1 TODO
171              
172             =over 4
173              
174             =item *
175              
176             Some syntax to allow a field to be associated with the accessor, so it can
177             automatically generate the C code, and assist the C code. This would
178             permit an alternative form where code blocks for value validation check and
179             post-update trigger were specified instead.
180              
181             Perhaps:
182              
183             field $x;
184              
185             accessor x {
186             field($x)
187             check ($new) { $new >= 0 or croak "Must be non-negative"; }
188             trigger { $self->updated; }
189             }
190              
191             =item *
192              
193             Integration with the constructor to permit a named-parameter at construction
194             time that would automatically set the value of the accessor, as if the user
195             had done so manually.
196              
197             Perhaps:
198              
199             accessor x :param { ... }
200              
201             =item *
202              
203             Consider whether to permit the accessor method itself to take arguments,
204             allowing for some kind of indexed or parametric accessor.
205              
206             Perhaps:
207              
208             field @palette;
209              
210             accessor colour($index) {
211             get { return $palette[$index]; }
212             set ($new) { $palette[$index] = $new; }
213             }
214              
215             =back
216              
217             =cut
218              
219             =head1 AUTHOR
220              
221             Paul Evans
222              
223             =cut
224              
225             0x55AA;