Tied arrays#
A tied array has two layers of interface. The lower layer - FETCH, STORE, FETCHSIZE, STORESIZE, CLEAR - is mandatory and handles element access and resizing. The upper layer - PUSH, POP, SHIFT, UNSHIFT, SPLICE, DELETE, EXISTS - is optional and lets the array respond to the matching built-ins; without a given hook, that built-in either falls back to the lower layer or dies.
The full menu is on the tie reference page. This chapter builds a class, then explains the size protocol and the optional mutators, with their call timing verified against the running interpreter.
A complete class#
A numeric array whose empty slots read back as 0 instead of undef, and whose STORESIZE pads new slots with 0:
use v5.36;
package ZeroFill {
sub TIEARRAY { my $class = shift; return bless { data => [] }, $class }
sub FETCH { my ($self, $i) = @_; return $self->{data}[$i] // 0 }
sub STORE { my ($self, $i, $v) = @_; $self->{data}[$i] = $v }
sub FETCHSIZE { my $self = shift; return scalar @{ $self->{data} } }
sub STORESIZE {
my ($self, $n) = @_;
my $data = $self->{data};
if ($n > @$data) {
push @$data, (0) x ($n - @$data); # pad with 0, not undef
} else {
$#$data = $n - 1;
}
}
sub CLEAR { my $self = shift; $self->{data} = [] }
sub PUSH { my $self = shift; push @{ $self->{data} }, @_; return scalar @{ $self->{data} } }
sub POP { my $self = shift; return pop @{ $self->{data} } }
}
tie my @a, 'ZeroFill';
$a[0] = 11;
$a[2] = 33; # FETCH on the skipped slot 1 reads back 0
say scalar @a; # 3
say $a[1]; # 0
$#a = 4; # STORESIZE grows the array, padding with 0
say "@a"; # 11 0 33 0 0
push @a, 44;
say pop @a; # 44
The object is a blessed hash ref wrapping a real array in its data slot - a common pattern, because the class usually wants room for state beyond the array itself.
The mandatory layer#
TIEARRAY classname, LISTThe constructor; returns a blessed reference that becomes the tied object.
FETCH this, index/STORE this, index, valueSingle-element read and write. A negative index is translated to a positive one - the runtime calls
FETCHSIZEfirst and adds it to the index - unless the class sets$NEGATIVE_INDICESto a true value, in which case the raw negative index reachesFETCHandSTORE.FETCHSIZE thisReturns the number of elements. This backs
scalar @arrayand the high-water mark for$#array. It is the single most-called method on a tied array, since the runtime needs the size to normalise negative indices and to bound iteration.STORESIZE this, countSets the array’s length. Growing should fill the new slots with the class’s notion of “empty” (here,
0); shrinking should drop the excess. This backs$#array = Nand assignment-driven resizing.CLEAR thisEmpties the array - triggered by
@array = ().
The size protocol in practice#
FETCHSIZE and STORESIZE are where tied arrays differ most from tied hashes. Three things to keep straight:
$#areads throughFETCHSIZEand writes throughSTORESIZE. Setting$#a = 4callsSTORESIZE($obj, 5).Skipped slots are the class’s problem. Assigning
$a[2]when the array has length 1 callsSTORE($obj, 2, ...)directly - the runtime does not callSTORESIZEto fill the gap. If a read of slot 1 should return0rather thanundef,FETCHhas to say so (the// 0in the example).EXTENDis advisory. When the runtime expects an array to growfor instance just before a large assignment - it may call
EXTEND this, countas a hint to pre-allocate. It is always safe to leaveEXTENDundefined or empty; it is purely an optimisation opportunity, and making it behave likeSTORESIZEis a bug, because the runtime callsEXTENDat times when it does not actually want the visible length to change.
The optional mutators#
Each of push, pop, shift, unshift, splice, delete, and exists dispatches to the uppercase hook of the same name when the class defines it. Their call timing, confirmed against the interpreter:
push @a, 1, 2→ onePUSHcall with the whole list.pop @a→POP;shift @a→SHIFT;unshift @a, 0→UNSHIFT.splice @a, 1, 1, 'x', 'y'→ oneSPLICEcall carrying offset, length, and the replacement list; its return value becomessplice’s.exists $a[0]→EXISTS;delete $a[0]→DELETE.
When a mutator hook is absent, behaviour depends on the operator. Tie::Array (below) supplies fallbacks for the common five in terms of the mandatory layer; without that base class, calling an unimplemented mutator dies at the point of use.
Call timing#
$a[$i]→FETCH;$a[$i] = $v→STORE.scalar @a, normalising a negative index →FETCHSIZE.$#a = $n,@a = (...)resizing →STORESIZE(andCLEARfirst for whole-array assignment).push/pop/shift/unshift/splice/delete/exists→ the matching uppercase hook when defined.An array slice
@a[1, 3]callsFETCHonce per index; there is no slice hook.
Minimum viable class#
The smallest tied array defines TIEARRAY, FETCH, STORE, FETCHSIZE, and STORESIZE. With those five, element access, scalar @a, $#a, and iteration all work. Add CLEAR so @a = () behaves. Add the mutators only for the built-ins your callers actually use - or inherit them.
The easy way: inherit from Tie::Array#
Tie::Array implements PUSH, POP, SHIFT, UNSHIFT, and SPLICE in terms of the mandatory five, so a subclass that supplies TIEARRAY, FETCH, STORE, FETCHSIZE, and STORESIZE gets all the list mutators for free:
package Logging {
use Tie::Array;
our @ISA = ('Tie::Array');
sub TIEARRAY { my $class = shift; return bless { data => [] }, $class }
sub FETCH { my ($self, $i) = @_; return $self->{data}[$i] }
sub STORE { my ($self, $i, $v) = @_; warn "store $i\n"; $self->{data}[$i] = $v }
sub FETCHSIZE { my $self = shift; return scalar @{ $self->{data} } }
sub STORESIZE { my ($self, $n) = @_; $#{ $self->{data} } = $n - 1 }
}
tie my @log, 'Logging';
push @log, 'x', 'y'; # PUSH inherited; calls STORE twice (warns)
say "@log"; # x y
push @log, 'x', 'y' works without a PUSH method because the inherited one expands into two STORE calls - which is why the example warns twice. Tie::Array does not define TIEARRAY itself, so the subclass must supply the constructor; its default DELETE and EXISTS simply croak, so override them if your callers need delete and exists.
See also#
tie- the built-in and the full array method menutied- reach the backing objectsplice- the most intricate mutator theSPLICEhook stands in forpushandpop- the mutatorsTie::Arrayderives from the mandatory layerTied hashes - the other rich container interface
Tie::Array- the base-class module that supplies the list mutators