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.
When you say: |
Perl translates it to: |
---|---|
tie @array, 'Foo',1,2 |
$obj = Foo->TIEARRAY (1,2); |
$a = $array[5]; |
$obj->FETCH(5); |
$array[5] = "aa" |
$obj->STORE(5, "aa"); |
untie @array; |
$obj->DESTROY(); |
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 n th 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.)
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: $!\n"; 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> # in a scalar context } sub STORE { die "Sorry. Cannot update file using package TieFile\n"; } 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, Persistence .