- publishing free software manuals
Perl Language Reference Manual
by Larry Wall and others
Paperback (6"x9"), 724 pages
ISBN 9781906966027
RRP £29.95 ($39.95)

Sales of this book support The Perl Foundation! Get a printed copy>>>

18.2 Tying Arrays

A class implementing a tied ordinary array should define the following methods: TIEARRAY, FETCH, STORE, FETCHSIZE, STORESIZE and perhaps UNTIE and/or DESTROY.

FETCHSIZE and STORESIZE are used to provide $#array and equivalent scalar(@array) access.

The methods POP, PUSH, SHIFT, UNSHIFT, SPLICE, DELETE, and EXISTS are required if the perl operator with the corresponding (but lowercase) name is to operate on the tied array. The Tie::Array class can be used as a base class to implement the first five of these in terms of the basic methods above. The default implementations of DELETE and EXISTS in Tie::Array simply croak.

In addition EXTEND will be called when perl would have pre-extended allocation in a real array.

For this discussion, we'll implement an array whose elements are a fixed size at creation. If you try to create an element larger than the fixed size, you'll take an exception. For example:

use FixedElem_Array;
tie @array, 'FixedElem_Array', 3;
$array[0] = 'cat';  # ok.
$array[1] = 'dogs'; # exception, length('dogs') > 3.

The preamble code for the class is as follows:

package FixedElem_Array;
use Carp;
use strict;
TIEARRAY classname, LIST
This is the constructor for the class. That means it is expected to return a blessed reference through which the new array (probably an anonymous ARRAY ref) will be accessed. In our 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 {ELEMSIZE} field will store the maximum element size allowed, and the {ARRAY} field will hold the true ARRAY ref. If someone outside the class tries to dereference the object returned (doubtless thinking it an ARRAY ref), they'll blow up. This just goes to show you that you should respect an object's privacy.
sub TIEARRAY {
  my $class    = shift;
  my $elemsize = shift;
  if ( @_ || $elemsize =~ /\D/ ) {
    croak "usage: tie ARRAY, '" . __PACKAGE__ . "', elem_size";
  }
  return bless {
    ELEMSIZE => $elemsize,
    ARRAY    => [],
  }, $class;
}
FETCH this, index
This method will be triggered every time an individual element the tied array is accessed (read). It takes one argument beyond its self reference: the index whose value we're trying to fetch.
sub FETCH {
  my $self  = shift;
  my $index = shift;
  return $self->{ARRAY}->[$index];
}
If a negative array index is used to read from an array, the index will be translated to a positive one internally by calling FETCHSIZE before being passed to FETCH. You may disable this feature by assigning a true value to the variable $NEGATIVE_INDICES in the tied array class. As you may have noticed, the name of the FETCH method (et al.) is the same for all accesses, even though the constructors differ in names (TIESCALAR vs TIEARRAY). While in theory you could have the same class servicing several tied types, in practice this becomes cumbersome, and it's easiest to keep them at simply one tie type per class.
STORE this, index, value
This method will be triggered every time an element in the tied array is set (written). It takes two arguments beyond its self reference: the index at which we're trying to store something and the value we're trying to put there. In our example, undef is really $self->{ELEMSIZE} number of spaces so we have a little more work to do here:
sub STORE {
  my $self = shift;
  my( $index, $value ) = @_;
  if ( length $value > $self->{ELEMSIZE} ) {
    croak "length of $value is greater than $self->{ELEMSIZE}";
  }
  # fill in the blanks
  $self->EXTEND( $index ) if $index > $self->FETCHSIZE();
  # right justify to keep element size for smaller elements
  $self->{ARRAY}->[$index] = sprintf "%$self->{ELEMSIZE}s",
                               $value;
}
Negative indexes are treated the same as with FETCH.
FETCHSIZE this
Returns the total number of items in the tied array associated with object this. (Equivalent to scalar(@array)). For example:
sub FETCHSIZE {
  my $self = shift;
  return scalar @{$self->{ARRAY}};
}
STORESIZE this, count
Sets the total number of items in the tied array associated with object this to be count. If this makes the array larger then class's mapping of undef should be returned for new positions. If the array becomes smaller then entries beyond count should be deleted. In our example, 'undef' is really an element containing $self->{ELEMSIZE} number of spaces. Observe:
sub STORESIZE {
  my $self  = shift;
  my $count = shift;
  if ( $count > $self->FETCHSIZE() ) {
    foreach ( $count - $self->FETCHSIZE() .. $count ) {
      $self->STORE( $_, ” );
    }
  } elsif ( $count < $self->FETCHSIZE() ) {
    foreach ( 0 .. $self->FETCHSIZE() - $count - 2 ) {
      $self->POP();
    }
  }
}
EXTEND this, count
Informative call that array is likely to grow to have count entries. Can be used to optimize allocation. This method need do nothing. In our example, we want to make sure there are no blank (undef) entries, so EXTEND will make use of STORESIZE to fill elements as needed:
sub EXTEND {   
  my $self  = shift;
  my $count = shift;
  $self->STORESIZE( $count );
}
EXISTS this, key
Verify that the element at index key exists in the tied array this. In our example, we will determine that if an element consists of $self->{ELEMSIZE} spaces only, it does not exist:
sub EXISTS {
  my $self  = shift;
  my $index = shift;
  return 0 if ! defined $self->{ARRAY}->[$index] ||
           $self->{ARRAY}->[$index] eq ' ' x $self->{ELEMSIZE};
  return 1;
}
DELETE this, key
Delete the element at index key from the tied array this. In our example, a deleted item is $self->{ELEMSIZE} spaces:
sub DELETE {
  my $self  = shift;
  my $index = shift;
  return $self->STORE( $index, ” );
}
CLEAR this
Clear (remove, delete, ...) all values from the tied array associated with object this. For example:
sub CLEAR {
  my $self = shift;
  return $self->{ARRAY} = [];
}
PUSH this, LIST
Append elements of LIST to the array. For example:
sub PUSH {  
  my $self = shift;
  my @list = @_;
  my $last = $self->FETCHSIZE();
  $self->STORE( $last + $_, $list[$_] ) foreach 0 .. $#list;
  return $self->FETCHSIZE();
}
POP this
Remove last element of the array and return it. For example:
sub POP {
  my $self = shift;
  return pop @{$self->{ARRAY}};
}
SHIFT this
Remove the first element of the array (shifting other elements down) and return it. For example:
sub SHIFT {
  my $self = shift;
  return shift @{$self->{ARRAY}};
}
UNSHIFT this, LIST
Insert LIST elements at the beginning of the array, moving existing elements up to make room. For example:
sub UNSHIFT {
  my $self = shift;
  my @list = @_;
  my $size = scalar( @list );
  # make room for our list
  @{$self->{ARRAY}}[ $size .. $#{$self->{ARRAY}} + $size ]
   = @{$self->{ARRAY}};
  $self->STORE( $_, $list[$_] ) foreach 0 .. $#list;
}
SPLICE this, offset, length, LIST
Perform the equivalent of splice on the array. offset is optional and defaults to zero, negative values count back from the end of the array. length is optional and defaults to rest of the array. LIST may be empty. Returns a list of the original length elements at offset. In our example, we'll use a little shortcut if there is a LIST:
sub SPLICE {
  my $self   = shift;
  my $offset = shift || 0;
  my $length = shift || $self->FETCHSIZE() - $offset;
  my @list   = (); 
  if ( @_ ) {
    tie @list, __PACKAGE__, $self->{ELEMSIZE};
    @list   = @_;
  }
  return splice @{$self->{ARRAY}}, $offset, $length, @list;
}
UNTIE this
Will be called when untie happens. (See 18.6 below.)
DESTROY this
This method will be triggered when the tied variable needs to be destructed. As with the scalar tie class, this is almost never needed in a language that does its own garbage collection, so this time we'll just leave it out.
ISBN 9781906966027Perl Language Reference ManualSee the print edition