Changing Hash Behaviour with tie
by Dave Cross
|
Pages: 1, 2, 3
A First Example: Tie::Hash::FixedKeys
Let's take a look at the implementation of Tie::Hash::FixedKeys. This module is available on CPAN if you want to take a closer look.
Writing the module is made far easier for use by the existence of a package called Tie::StdHash. This is a tied hash that mirrors the behavior of a standard Perl hash. This package is stored in the module Tie::Hash. This means that if you wrote code like the following example, then you would have a tied hash that acts the same way as a 'real' hash.
use Tie::Hash;
my %hash;
tie %hash, 'Tie::StdHash';
|
|
So far, so good. But it hasn't really achieved much. The hash %hash
is now a tied object, but we haven't changed any of its functionalities.
Tie::StdHash works much better if it is used as a base class from which
you inherit behavior. For example, the start of the Tie::Hash::FixedKeys
class looks like this:
package Tie::Hash::FixedKeys;
use strict;
use Tie::Hash;
use Carp;
use vars qw(@ISA);
@ISA = qw(Tie::StdHash);
This is standard for a Perl object, but notice that we've loaded the
Tie::Hash module (with use Tie::Hash) and have told our package
to inherit behavior from Tie::StdHash by putting Tie::StdHash in
the @ISA package variable.
If we stopped there, our Tie::Hash::FixedKeys package would have the
same behavior as a standard Perl hash. This is because each time Perl
tried to find one of the tie interface methods (like FETCH or
STORE) in our package it would fail and would call the version found
in our parent class, Tie::StdHash.
At this point we can start to change the standard hash
behavior by simply overriding the methods that we want to change.
We'll start by implementing the TIEHASH method differently.
sub TIEHASH {
my $class = shift;
my %hash;
@hash{@_} = (undef) x @_;
bless \%hash, $class;
}
The TIEHASH function is passed the name of the class as its first
parameter, so we shift that into $class in the first line. The
rest of the parameters in @_ are whatever extra parameters have been
passed into the tie call. In the example of how to use our
proposed class at the start of this article, we passed it the list
of valid keys. Therefore, we take this list of keys and (using a hash
slice) we initialize a hash so that it has undef as the value for
each of these keys. Finally, we take a reference to this hash, bless
it into the required class and return the reference.
It's worth pointing out here, the one caveat about using Tie::StdHash.
In order to use the default behavior, your new class must be based
on a hash reference and this hash must contain only real hash data.
We couldn't, for example, invent a key called _keys that would
contain a list of valid key names as, for example, this key would be
shown if the user called the keys method.
At this point we have a hash that has values (of undef) for each
of the allowed keys. This doesn't yet prevent us from adding new keys.
For that we need to override the STORE method.
sub STORE {
my ($self, $key, $val) = @_;
unless (exists $self->{$key}) {
croak "invalid key [$key] in hash\n";
return;
}
$self->{$key} = $val;
}
The three parameters passed to the STORE method are a reference to
the tied object, and a new key/value pair. We need the STORE method
to prevent new keys being added to the underlying hash, and we achieve
that by checking that the given key exists before setting the value.
Note that as our underlying object is a real hash, we can check this
simply by using the exists function. If the key doesn't exist we
give the user a friendly warning and return from the method without
changing the hash.
We have now prevented the hash from growing by adding keys, but it is
still possible to remove keys from the hash (and our STORE
implementation would prevent them from being set once they had been
removed), so we also need to override the implementation of DELETE.
sub DELETE {
my ($self, $key) = @_;
return unless exists $self->{$key};
my $ret = $self->{$key};
$self->{$key} = undef;
return $ret;
}
Once again, we don't actually want to change the existing set of keys in
the hash, so we check to see whether the key already exists and return immediately if
it doesn't. If the key does exist, then we don't want to actually delete it,
so we simply set the value back to undef. Notice that we note the
value before deleting it so that we can return it from the method, thus
mimicking the behavior of the real delete function.
There's one other way to affect the keys in our hash. Code like this:
%hash = ();
will cause the CLEAR method to be called. The default behavior for
this method is to remove all of the data from the hash. We need to
replace this with a method that will reset all of the values to
undef without changing the keys in any way.
sub CLEAR {
my $self = shift;
$self->{$_} = undef foreach keys %$self;
}
And that's all that we need to do. All of the other functionality of a
standard hash is inherited from Tie::StdHash. You can fetch values from
our hash as normal without us writing any more lines of code. Built-in
Perl functions like each and keys also work as expected.


