A class implementing a tied array must define at least
the methods TIEARRAY
, FETCH
, and
STORE
. There are many optional methods: the
ubiquitous DESTROY
method, of course, but also the
STORESIZE
and FETCHSIZE
methods
used to provide $#array
and
scalar(@array)
access. In addition,
CLEAR
is triggered when Perl needs to empty the
array, and EXTEND
when Perl would have pre-extended
allocation in a real array.
You may also define the POP
,
PUSH
, SHIFT
,
UNSHIFT
, SPLICE
,
DELETE
, and EXISTS
methods if
you want the corresponding Perl functions to work on the tied array.
The Tie::Array
class can serve as a base class to
implement the first five of those functions in terms of
FETCH
and STORE
.
(Tie::Array
's default implementation of
DELETE
and EXISTS
simply calls
croak
.) As long as you define
FETCH
and STORE
, it doesn't
matter what kind of data structure your object contains.
On the other hand, the Tie::StdArray
class
(defined in the standard Tie::Array
module)
provides a base class with default methods that assume the object
contains a regular array. Here's a simple array-tying class that makes
use of this. Because it uses Tie::StdArray
as its
base class, it only needs to define the methods that should be treated
in a nonstandard way.
#!/usr/bin/perl package ClockArray; use Tie::Array; our @ISA = 'Tie::StdArray'; sub FETCH { my($self,$place) = @_; $self->[ $place % 12 ]; } sub STORE { my($self,$place,$value) = @_; $self->[ $place % 12 ] = $value; } package main; tie my @array, 'ClockArray'; @array = ( "a" … "z" ); print "@array ";
When run, the program prints out "y z o p q r s t u v w
x
". This class provides an array with only a dozen slots,
like hours of a clock, numbered 0 through 11. If you ask for the 15th
array index, you really get the 3rd one. Think of it as a travel aid
for people who haven't learned how to read 24-hour clocks.
That's the simple way. Now for some nitty-gritty details. To demonstrate, we'll implement an array whose bounds are fixed at its creation. If you try to access anything beyond those bounds, an exception is raised. For example:
use BoundedArray; tie @array, "BoundedArray", 2; $array[0] = "fine"; $array[1] = "good"; $array[2] = "great"; $array[3] = "whoa"; # Prohibited; displays an error message.
The preamble code for the class is as follows:
package BoundedArray; use Carp; use strict;
To avoid having to define
SPLICE
later, we'll inherit from the
Tie::Array
class:
use Tie::Array; our @ISA = ("Tie::Array");
CLASSNAME
->TIEARRAY(
LIST
)
As the constructor for the class,
TIEARRAY
should return a blessed reference
through which the tied array will be emulated.
In this next example, just to show you that you don't
really have to return an array reference,
we'll choose a hash reference to represent our object. A hash
works out well as a generic record type: the value in the
hash's "BOUND
" key will store the maximum
bound allowed, and its "DATA
" value will
hold the actual data. If someone outside the class tries to
dereference the object returned (doubtless thinking it an
array reference), an exception is raised.
sub TIEARRAY { my $class = shift; my $bound = shift; confess "usage: tie(@ary, 'BoundedArray', max_subscript)" if @_ || $bound =~ /D/; return bless { BOUND => $bound, DATA => [] }, $class; }
We can now say:
tie(@array, "BoundedArray", 3); # maximum allowable index is 3
to ensure that the array will never have more than four
elements. Whenever an individual element of the array is
accessed or stored, FETCH
and
STORE
will be called just as they were for
scalars, but with an extra index argument.
SELF
->FETCH(
INDEX
)
This method is run whenever an individual element in the tied array is accessed. It receives one argument after the object: the index of the value we're trying to fetch.
sub FETCH { my ($self, $index) = @_; if ($index > $self->{BOUND}) { confess "Array OOB: $index > $self->{BOUND}"; } return $self->{DATA}[$index]; }
SELF
->STORE(
INDEX
,
VALUE
)
This method is invoked whenever an element in the tied array is set. It takes two arguments after the object: the index at which we're trying to store something and the value we're trying to put there. For example:
sub STORE { my($self, $index, $value) = @_; if ($index > $self->{BOUND} ) { confess "Array OOB: $index > $self->{BOUND}"; } return $self->{DATA}[$index] = $value; }
SELF
->DESTROY
Perl calls this method when the tied variable needs to be destroyed and its memory reclaimed. This is almost never needed in a language with garbage collection, so for this example we'll just leave it out.
SELF
->FETCHSIZE
The FETCHSIZE
method should return
the total number of items in the tied array associated with
SELF
. It's equivalent to
scalar(@array)
, which is usually equal to
$#array + 1
.
sub FETCHSIZE { my $self = shift; return scalar @{$self->{DATA}}; }
SELF
->STORESIZE(
COUNT
)
This method sets the total number of items in the tied
array associated with SELF
to be
COUNT
. If the array shrinks, you
should remove entries beyond COUNT
.
If the array grows, you should make sure the new positions are
undefined. For our BoundedArray
class, we
also ensure that the array doesn't grow beyond the limit
initially set.
sub STORESIZE { my ($self, $count) = @_; if ($count > $self->{BOUND}) { confess "Array OOB: $count > $self->{BOUND}"; } $#{$self->{DATA}} = $count; }
SELF
->EXTEND(
COUNT
)
Perl uses the EXTEND
method to
indicate that the array is likely to expand to hold
COUNT
entries. That way you can can
allocate memory in one big chunk instead of in many successive
calls later on. Since our BoundedArray
s
have fixed upper bounds, we won't define this method.
SELF
->EXISTS(
INDEX
)
This method verifies that the element at
INDEX
exists in the tied array. For
our BoundedArray
, we just employ Perl's
built-in exists
after verifying that it's
not an attempt to look past the fixed upper bound.
sub EXISTS { my ($self, $index) = @_; if ($index > $self->{BOUND}) { confess "Array OOB: $index > $self->{BOUND}"; } exists $self->{DATA}[$index]; }
SELF
->DELETE(
INDEX
)
The DELETE
method removes the element
at INDEX
from the tied array
SELF
. For our
BoundedArray
class, the method looks nearly
identical to EXISTS
, but this is not the
norm.
sub DELETE { my ($self, $index) = @_; print STDERR "deleting! "; if ($index > $self->{BOUND}) { confess "Array OOB: $index > $self->{BOUND}"; } delete $self->{DATA}[$index]; }
SELF
->CLEAR
This method is called whenever the array has to be
emptied. That happens when the array is set to a list of new
values (or an empty list), but not when it's provided to the
undef
function. Since a cleared
BoundedArray
always satisfies the upper
bound, we don't need check anything here:
sub CLEAR { my $self = shift; $self->{DATA} = []; }
If you set the array to a list, CLEAR
will trigger but won't see the list values. So if you violate
the upper bound like so:
tie(@array, "BoundedArray", 2); @array = (1, 2, 3, 4);
the CLEAR
method will still return
successfully. The exception will only be raised on the
subsequent STORE
. The assignment triggers
one CLEAR
and four
STORE
s.
SELF
->PUSH(
LIST
)
This method appends the elements of
LIST
to the array. Here's how it
might look for our BoundedArray
class:
sub PUSH { my $self = shift; if (@_ + $#{$self->{DATA}} > $self->{BOUND}) { confess "Attempt to push too many elements"; } push @{$self->{DATA}}, @_; }
SELF
->UNSHIFT(
LIST
)
This method prepends the elements of
LIST
to the array. For our
BoundedArray
class, the subroutine would be
similar to PUSH
.
SELF
->POP
The POP
method removes the
last element of the array and returns it. For
BoundedArray
, it's a one-liner:
sub POP { my $self = shift; pop @{$self->{DATA}} }
SELF
->SHIFT
The SHIFT
method removes the first
element of the array and returns it. For
BoundedArray
, it's similar to
POP
.
SELF
->SPLICE(
OFFSET
,
LENGTH
,
LIST
)
This method lets you splice the
SELF
array. To mimic Perl's
built-in splice
,
OFFSET
should be optional and
default to zero, with negative values counting back from the
end of the array. LENGTH
should
also be optional, defaulting to rest of the array.
LIST
can be empty. If it's properly
mimicking the built-in, the method will return a list of the
original LENGTH
elements at
OFFSET
(that is, the list of
elements to be replaced by LIST
).
Since splicing is a somewhat complicated operation, we
won't define it at all; we'll just use the
SPLICE
subroutine from the
Tie::Array
module that we got for free when
we inherited from Tie::Array
. This way we
define SPLICE
in terms of other
BoundedArray
methods, so the bounds
checking will still occur.
That completes our BoundedArray
class. It
warps the semantics of arrays just a little. But we can do better,
and in very much less space.
One of the nice things about variables is that they interpolate. One of the not-so-nice things about functions is that they don't. You can use a tied array to make a function that can be interpolated. Suppose you want to interpolate random integers in a string. You can just say:
#!/usr/bin/perl package RandInterp; sub TIEARRAY { bless my $self }; sub FETCH { int rand $_[1] }; package main; tie @rand, "RandInterp";for (1,10,100,1000) { print "A random integer less than $_ would be $rand[$_] "; } $rand[32] = 5; # Will this reformat our system disk?
When run, this prints:
A random integer less than 1 would be 0 A random integer less than 10 would be 3 A random integer less than 100 would be 46 A random integer less than 1000 would be 755 Can't locate object method "STORE" via package "RandInterp" at foo line 10.
As you can see, it's no big deal that we didn't even implement
STORE
. It just blows up like normal.