File Coverage

blib/lib/Class/ArrayObjects.pm
Criterion Covered Total %
statement 48 50 96.0
branch 9 12 75.0
condition 4 7 57.1
subroutine 5 6 83.3
pod n/a
total 66 75 88.0


line stmt bran cond sub pod time code
1              
2             ### ###
3             # Class::ArrayObjects - Utility class for array based objects #
4             # Robin Berjon #
5             # ------------------------------------------------------------------- #
6             # 01/12/2003 - v1.02 support non-existing "with" #
7             # 07/07/2003 - v1.01 patch by Slaven Rezic so that "extend" will look #
8             # into @ISA in case the class isn't specified. #
9             # 06/07/2002 - v1.00 clean up #
10             # 21/04/2001 - v0.04 many many documentation updates + release #
11             # 02/04/2001 - v0.03 feature upgrade in view of release #
12             # 28/01/2001 - v0.02 a few enhancements and uses #
13             # 12/12/2000 - v0.01 initial hack #
14             ### ###
15              
16              
17             package Class::ArrayObjects;
18              
19 1     1   8249 use strict;
  1         3  
  1         36  
20 1     1   4 no strict 'refs';
  1         2  
  1         24  
21 1     1   4 use vars qw($VERSION %packages);
  1         6  
  1         280  
22              
23             $VERSION = '1.03';
24              
25              
26             #---------------------------------------------------------------------#
27             # import()
28             # this is where all the work gets done, at load
29             #---------------------------------------------------------------------#
30             sub import {
31 4     4   151 my $class = shift;
32 4 50       12 @_ or return;
33              
34 4         11 my $pkg = caller;
35 4         6 my $method = shift;
36 4         3 my $options = shift;
37              
38             ### grab the start index and the fields
39 4         6 my ($idx, @fld, @real_fld);
40              
41             # for basic definition
42 4 100       15 if ($method eq 'define') {
    50          
43 1   50     5 $options->{fields} ||= [];
44 1         3 $idx = 0;
45 1         1 @fld = @{$options->{fields}};
  1         7  
46 1         3 @real_fld = @fld;
47             }
48              
49             # for extension
50             elsif ($method eq 'extend') {
51 3   50     9 $options->{with} ||= [];
52             {
53 1     1   5 no strict 'refs';
  1         3  
  1         536  
  3         4  
54 3 100 66     13 if (not defined $options->{class} and @{ $pkg . '::ISA' } == 1) {
  1         7  
55 1         7 $options->{class} = ${ $pkg . '::ISA' }[0];
  1         5  
56             }
57             }
58 3 50       11 die "[$pkg]: can't extend undefined class $options->{class} with package $pkg"
59             unless defined $packages{$options->{class}};
60              
61             # get what is needed to store the real idx
62 3         4 @real_fld = (@{$packages{$options->{class}}}, @{$options->{with}});
  3         7  
  3         27  
63              
64             # support import of parent fields
65 3 100       8 if ($options->{import}) {
66 1         2 $idx = 0;
67 1         4 @fld = @real_fld;
68             }
69             else {
70 2         3 $idx = $#{$packages{$options->{class}}} + 1;
  2         6  
71 2         4 @fld = @{$options->{with}};
  2         13  
72             }
73             }
74              
75             # there was an error
76             else {
77 0         0 die "[$pkg]: first arg must be 'define' or 'extend'";
78             }
79              
80             # now lets create the subs
81 4         8 for my $enum (@fld) {
82 11         21 my $qname = "${pkg}::$enum";
83 11         14 my $value = $idx;
84 11     0   116 *$qname = sub () { $value };
  0         0  
85 11         28 $idx++;
86              
87             # another way of doing it:
88             # eval "sub ${pkg}::$enum () { $idx }";
89             # die "[$pkg]: $@" if $@;
90             # $idx++
91             }
92 4         10 $packages{$pkg} = \@real_fld; # store the fields for extension
93 4         2422 return 1;
94             }
95             #---------------------------------------------------------------------#
96              
97              
98              
99             1;
100             =pod
101              
102             =head1 NAME
103              
104             Class::ArrayObjects - utility class for array based objects
105              
106             =head1 SYNOPSIS
107              
108             package Some::Class;
109             use Class::ArrayObjects define => {
110             fields => [qw(_foo_ _bar_ BAZ)],
111             };
112              
113             or
114              
115             package Other::Class;
116             use base 'Some::Class';
117             use Class::ArrayObjects extend => {
118             class => 'Some::Class',
119             with => [qw(_zorg_ _fnord_ BEZ)],
120             import => 1,
121             };
122              
123             =head1 DESCRIPTION
124              
125             This module is little more than a cute way of defining constant subs
126             in your own package. Constant subs are very useful when dealing with
127             array based objects because they allow one to access array slots by
128             name instead of by index.
129              
130             =head2 Why use arrays for objects instead of hashes ?
131              
132             There are two apparently compelling reasons to use arrays for objects
133             instead of hashes.
134              
135             First: speed. In my benchmarks on a few boxes around here I've seen
136             arrays be faster by 30%. I must admit that my benchmarks weren't
137             perfect as I wasn't all that interested in speed per se, only in
138             knowing whether I was to take a serious performance hit or not (I was
139             nevertheless pleasantly surprised to note the opposite, it can't
140             hurt).
141              
142             Second: memory. Memory was much more important to me as I was
143             targeting a mod_perl environment where every bit of memory tends to
144             count. Depending on how they are used, arrays use from 30% up to 65%
145             less space than hashes. As a rule of thumb the more keys you have, the
146             more you may save.
147              
148             It must be said though that despite the fact that I happened to be
149             looking for ways to save space, it's not a reason to jump into array
150             based objects and start converting every single hash you have to an
151             array. Yes, I did see some of my processes lose I<~3Mo> of unshared
152             memory so there are definitely cases when it's useful. Such cases are
153             usually when you have lots of objects and/or structures that are
154             fairly similar in nature (ie have the same keys) but contain different
155             values. I don't know how Perl works internally but it would seem only
156             logical that it has to store the keys with every hash, whereas using
157             arrays there are no keys (which is why this package exists: to provide
158             you with something that looks like keys into arrays).
159              
160             In addition to that, this package can be seen as twisting slightly the
161             view on how to do OO in Perl, encouraging some limited encapsulation
162             of fields and extension subclassing rather than override subclassing
163             (the latter really being a matter of taste).
164              
165             =head2 Why not pseudo-hashes ?
166              
167             Pseudo-hashes never appealed to me, they always seemed to have been
168             hacked on top of Perl. They never left experimental status, which
169             probably says a lot already. A number of things that work with hashes
170             and arrays don't work with them (and development seems to have
171             stopped). And overall, they usually end up not saving you any space
172             anyway. Pseudo-hashes must die.
173              
174             =head2 Why Class::ArrayObjects ?
175              
176             But why then use this class instead of the C or C
177             modules ? Because it adds extra sugar (yum).
178              
179             Its main advantage over C (imho of course) is that you don't
180             have to define the value of each field. Less typing, more readability.
181              
182             C also provides that plus but it enforces naming rules in a
183             way which I find limiting (it probably has very good reasons to do so,
184             but I think that they don't apply in the context of array based
185             objects). This module only complains if you try to use a field name
186             that isn't a valid Perl sub name. (Note: right now it doesn't even
187             complain because I was convinced when I wrote it that Perl would. But
188             it turns out that you are perfectly allowed to define a sub with a
189             forbidden name. Whether this is a bug or a feature, I don't know).
190              
191             And last but not least, it defines a way to allow for inheritance
192             while using array based objects. A major drawback of array based
193             objects is that unlike with hashes, if your base class adds a field,
194             you have to move all your fields' indices up by one. You shouldn't
195             have to know such things, or even to care about it.
196              
197             Here, instead of using the C option (which creates fields in
198             a class), simply use the C option. Tell it which class to
199             extend (it needs to be already loaded, and must use
200             Class::ArrayObjects to define its fields too) and which fields to add.
201             Class::ArrayObjects takes care of counting from the right index in
202             your subclass. It can't do multiple inheritance, and unless someone
203             hacks it in somehow I doubt it ever will.
204              
205             You may use the C option to require that your parents' fields
206             be defined in your own package too, so that you can access them. It is
207             off by default so that you can use fields with the same names as those
208             of your superclasse(s) (which is a plus over hash based objects) and
209             also to avoid defining subs all over your package without you knowing
210             about them.
211              
212             It may be worth noting that the added functionality doesn't get in the
213             way, and using this to define constants is just as fast as using
214             C or C.
215              
216             =head1 USING Class::ArrayObjects
217              
218             There are two ways to use Class::ArrayObjects, either to simply define
219             fields to use in your own objects, or to extend fields defined in a
220             superclass of your. In a wild burst of creative naming I thus spawned
221             into existence two options named respectively C and C.
222              
223             The way the two are used is the same:
224              
225             use Class::ArrayObjects I => I
226              
227             =head2 The define option
228              
229             package Some::Class;
230             use Class::ArrayObjects define => {
231             fields => [qw(_foo_ _bar_ BAZ)],
232             };
233              
234             C has only one option: C. It is an arrayref of strings
235             which are the names of the fields you wish to use. They can be anything,
236             so long as they are valid Perl sub names.
237              
238             =head2 The extend option
239              
240             package Other::Class;
241             use base 'Some::Class';
242             use Class::ArrayObjects extend => {
243             class => 'Some::Class',
244             with => [qw(_zorg_ _fnord_ BEZ)],
245             import => 1,
246             };
247              
248             C has three options:
249              
250             =over 4
251              
252             =item * class
253              
254             This defines the class to extend (it must also use Class::ArrayObjects
255             and have been loaded previously). If that class is not specified, it will
256             look at @ISA. If @ISA contains only one item it will use that one.
257              
258             =item * with
259              
260             This is exactly equivalent to C except that it reads better to
261             have extend class Foo with x,y,z.
262              
263             =item * import
264              
265             Defaulting to false, setting it to any true value will make your
266             superclasses' fields also defined in your package. This can be needed
267             at times, though I wouldn't encourage its use.
268              
269             =back
270              
271             =head2 After that ?
272              
273             After you've defined fields, all you have to do is use them as indices
274             to your arrays.
275              
276             package Some::Class;
277             use Class::ArrayObjects define => {
278             fields => [qw(_foo_ _bar_ BAZ)],
279             };
280              
281             my @arry = qw(zorg stuff blurp);
282             print $arry[_bar_]; # stuff
283              
284             Any operation you can do on arrays with numeric indices works exactly
285             the same way. The only difference is that you are using names, which
286             are much easier to remember. There is no performance penalty for this,
287             Perl is smart enough to inline the return values of constant subs so
288             that when in the above example you say _bar_ it really sees 1.
289              
290             =head2 A note to mod_perl users
291              
292             The contexts in which I use this module are mostly mod_perl related.
293             In fact, one of the reasons I created it was to allow for the space
294             efficient representation of many objects. It may be further
295             optimizable, but so far it has already seemed to work well.
296              
297             You can preload this module without defining any fields as follows:
298              
299             use Class::ArrayObjects qw();
300              
301             In that case, C will not be called and nothing will happen
302             other then the preloading of the code. As a precaution, even if it
303             were called it would return immediately.
304              
305             I do recommend that you preload all modules that are based on
306             Class::ArrayObjects so that the data it stores internally about which
307             fields belong to which classes (in order to allow for extension)
308             remains shared by all the processes.
309              
310             =head1 BUGS AND CAVEATS
311              
312             I don't know of any outstanding bugs presently but it is not
313             impossible that some may have filtered out. I have been using this
314             module in production for some time now, and it appears to be behaving
315             with stability.
316              
317             Of course, you mustn't define a field in your package with the same
318             name as another sub.
319              
320             As a rule of thumb, I find that this kind of class works better for
321             extension subclasses than for override subclasses, but YMMV.
322              
323             =head1 TODO
324              
325             - add an interface to allow people to mess with the internals on
326             demand
327             - add serialisation helpers to allow one to persist an object based
328             on Class::ArrayObjects and later retrieve it regardless of whether
329             the order of the fields have changed or not.
330              
331             =head1 ACKNOWLEDGMENTS
332              
333             Greg Bacon's for his article I
334             Arrays> which I read ages ago and inspired this module, one of the
335             first I put on CPAN.
336              
337             Slaven Rezic for the @ISA patch.
338              
339             =head1 AUTHOR
340              
341             Robin Berjon, robin@knowscape.com
342              
343             =head1 COPYRIGHT
344              
345             Copyright (c) 2000-2002 Robin Berjon. All rights reserved. This program is
346             free software; you can redistribute it and/or modify it under the same terms
347             as Perl itself.
348              
349             =head1 SEE ALSO
350              
351             nothing that I can think of...
352              
353             =cut
354