Tying an
array to a module runs along very similar lines, as shown in Table 9.2. There are two levels at which you can work
with a normal array. At one level, you can get and set the value of
the entire array and the last element’s index (using
$#array
). At another level, you can get or set
individual elements and create or destroy its elements using
splice
, push
,
pop
, and so on. As this book goes to print,
tie
handles reads and writes only to array
elements and does not allow the array itself to be modified in any
way. This situation is expected to be remedied in the not-too-distant
future.
One useful example of tied arrays is to emulate a bitset. If you set
the 200th element to 1, the module can set the 200th bit in a bit
array, using vec()
.
The next section shows an example of tied arrays to wrap a text file.
This example builds a facility called TieFile to make a text file appear as an array. If you want to examine the 20th line of foo.txt, for example, you write:
tie @lines, 'TieFile', 'foo.txt'; print $lines[20];
For simplicity, this module does not accept updates to any element.
When asked to fetch the nth line, the TieFile
module shown in Example 9.2 reads the file until it
reaches that line and returns it. Since it is wasteful to keep
traversing the entire file every time a line is requested, TieFile
keeps track of the file offsets of the beginning of each line so that
when you ask it for a line that it has already visited, it knows the
precise offset to seek
to before reading. The
object created by TIEARRAY
has two fields: one to
store this array of offsets and another to store the filehandle of
the open file. These two fields are stored in an anonymous array.
(Alternatively, you can use a hash or the ObjectTemplate module.)
Example 9-2. TieFile.pm: Mapping a File to an Array
package TieFile; use Symbol; use strict; # The object constructed in TIEARRAY is an array, and these are the # fields my $F_OFFSETS = 0; # List of file seek offsets (for each line) my $F_FILEHANDLE = 1; # Open filehandle sub TIEARRAY { my ($pkg, $filename) = @_; my $fh = gensym(); open ($fh, $filename) || die "Could not open file: $! "; bless [ [0], # 0th line is at offset 0 $fh ], $pkg; } sub FETCH { my ($obj, $index) = @_; # Have we already read this line? my $rl_offsets = $obj-->[$F_OFFSETS]; my $fh = $obj-->[$F_FILEHANDLE]; if ($index > @$rl_offsets) { $obj-->read_until ($index); } else { # seek to the appropriate file offset seek ($fh, $rl_offsets-->[$index], 0); } return (scalar <$fh>); # Return a single line, by evaluating <$fh> } sub STORE { die "Sorry. Cannot update file using package TieFile "; } sub DESTROY { my ($obj) = @_; # close the filehandle close($obj-->[$F_FILEHANDLE]); } sub read_until { my ($obj, $index) = @_; my $rl_offsets = $obj-->[$F_OFFSETS]; my $last_index = @$rl_offsets - 1; my $last_offset = $rl_offsets-->[$last_index]; my $fh = $obj-->[$F_FILEHANDLE]; seek ($fh, $last_offset, 0); my $buf; while (defined($buf = <$fh>)) { $last_offset += length($buf); $last_index++; push (@$rl_offsets, $last_offset); last if $last_index > $index; } } 1;
You may have noticed that this module works only if you assign strings or numbers to the tied array’s elements. If you assign it a reference, it simply converts it into a string and stores it into the file, which is patently useless when the data is read back from the file. In other words, this module should ideally “serialize” the data structure pointed to by the reference before storing it into the file, and recreate it when requested. We’ll have more to say on this subject in Chapter 10.