Sign In/My Account | View Cart  
advertisement


Listen Print

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';

The Perl CD BookshelfThe Perl CD Bookshelf
May 2001
0-596-00164-9, Order Number: 1649
672 pages, $79.95, Features CD-ROM

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.

Pages: 1, 2, 3

Next Pagearrow