Tying Hashes

A class implementing a tied hash should define eight methods. TIEHASH constructs new objects. FETCH and STORE access the key/value pairs. EXISTS reports whether a key is present in the hash, and DELETE removes a key along with its associated value.[2]CLEAR empties the hash by deleting all key/value pairs. FIRSTKEY and NEXTKEY iterate over the key/value pairs when you call keys, values, or each. And as usual, if you want to perform particular actions when the object is deallocated, you may define a DESTROY method. (If this seems like a lot of methods, you didn't read the last section on arrays attentively. In any event, feel free to inherit the default methods from the standard Tie::Hash module, redefining only the interesting ones. Again, Tie::StdHash assumes the implementation is also a hash.)

[2] Remember that Perl distinguishes between a key not existing in the hash and a key existing in the hash but having a corresponding value of undef. The two possibilities can be tested with exists and defined, respectively.

For example, suppose you want to create a hash where every time you assign a value to a key, instead of overwriting the previous contents, the new value is appended to an array of values. That way when you say:

$h{$k} = "one"; $h{$k} = "two";


It really does:

push @{ $h{$k} }, "one"; push @{ $h{$k} }, "two";


That's not a very complicated idea, so you should be able to use a pretty simple module. Using Tie::StdHash as a base class, it is. Here's a Tie::AppendHash that does just that:

package Tie::AppendHash; use Tie::Hash; our @ISA = ("Tie::StdHash"); sub STORE {
 my ($self, $key, $value) = @_; push @{$self->{key}}, $value;
}
1;


Hash-Tying Methods

Here's an example of an interesting tied-hash class: it gives you a hash representing a particular user's dot files (that is, files whose names begin with a period, which is a naming convention for initialization files under Unix). You index into the hash with the name of the file (minus the period) and get back that dot file's contents. For example:

use DotFiles; tie %dot, "DotFiles"; if ( $dot{profile} =~ /MANPATH/ or $dot{login} =~ /MANPATH/ or $dot{cshrc} =~ /MANPATH/ ) {
 print "you seem to set your MANPATH\n";
}


Here's another way to use our tied class:

# Third argument is the name of a user whose dot files we will tie to. tie %him, "DotFiles", "daemon"; foreach $f (keys %him) {
 printf "daemon dot file %s is size %d\n", $f, length $him{$f};
}


In our DotFiles example we implement the object as a regular hash containing several important fields, of which only the {CONTENTS} field will contain what the user thinks of as the hash. Here are the object's actual fields:
Field Contents
USER Whose dot files this object represents.
HOME Where those dot files live.
CLOBBER Whether we are allowed to change or remove those dot files.
CONTENTS The hash of dot file names and content mappings.

Here's the start of DotFiles.pm:

package DotFiles; use Carp; sub whowasi {
 (caller(1))[3] . "()"
}
my $DEBUG = 0; sub debug {
 $DEBUG = @_ ? shift : 1 }


For our example, we want to be able to turn on debugging output to help in tracing during development, so we set up $DEBUG for that. We also keep one convenience function around internally to help print out warnings: whowasi returns the name of the function that called the current function (whowasi's "grandparent" function).

Here are the methods for the DotFiles tied hash:

Now that we've given you all those methods, your homework is to go back and find the places we interpolated @{[&whowasi]} and replace them with a simple tied scalar named $whowasi that does the same thing.