|Previous||Table of Contents||Next|
10.2 Advanced Brevity
Perl provides some sophisticated forms of syntactic sugaring (ways of making certain coding constructions briefer and more intuitive). Two of these techniques are tieing and overloading.
We saw an example of tieing in Section 3.3. Tieing lets you take over the normal function of a variable. On the outside, it looks like a normal scalar, array, hash, or whatever. But underneath, whenever the variable is used, code you wrote gets called instead of what you'd expect to happen.
This looks on the face of it good mainly for practical jokes (and it certainly has that potential). However, it can also be used for making code more succinct and for gaining transparency.
Succinctness comes when you realize you are implementing functionality that behaves like a variable. For instance, if you have an electronic thermometer and code for reading the thermometer's value, you could certainly implement it with traditional procedural code:
use Thermometer; $temp = Thermometer->read;
But a thermometer behaves just like a scalar variable (albeit a read-only scalar variable; changing the reading on a thermometer does not change the temperature it measures, unless you know how to break the laws of thermodynamics). How much cooler, then (so to speak), to say:
tie $current_temp, 'Thermometer';
and then whenever you read $current_temp, it is magically updated with the current temperature.
The module Themometer.pm can accomplish that quite easily through implementing methods TIESCALAR and FETCH according to the perltie documentation.
The semantics of the functionality you are implementing need to map onto the natural behavior of one of Perl's variable types for this to work. Our thermometer class is not a perfect match for a scalar (because it is meaningless to attempt to write it, and therefore we have to decide whether to implement and document any such attempt as either throwing an exception or having no effect), but it's certainly close enough to be worth the effort. We can associate attributes with each tied variable when we first tie it, so we might choose to declare:
tie $current_temp, 'Thermometer', 'Celsius';
to make sure we know what units the temperature will be in when we read it. This interface is now looking much better than the procedural equivalent:
$current_temp = Thermometer->read_celsius;
or even the object-oriented equivalent:
$current_temp = Thermometer->new(scale => 'Celsius'); print "Temperature: ", $current_temp->read;
And that's just for a simple scalar. When you discover functionality that maps onto a hash, for instance, you can make seriously succinct code. The most common tied interface for a hash is a database that stores the contents of the hash and makes them persistent. I was implementing a program once that had to store a lot of entries in a hash; eventually it overtaxed the virtual memory resources of the machine I had. In a classic trade-off of memory for speed, I could have hunted down every access to the hash and replaced it with a call to a database library's fetch() or store() method. But that's more work than I like to do, and fortunately others had been equally lazy before me and encapsulated the fruits of their laziness in a series of modules for tieing hashes to databases. All I had to do was add one line:
and change the declaration of the hash from:
tie my %data, 'DB_File', $database, ...
(where the ... arguments specified the file to use as a database and related information), and I had no need to change any other code. Thereafter, every access to the hash behaved just as it did before, only it took a bit longer but didn't use up memory. Into the bargain I got persistence, meaning that I could recover the values from a previous run, enabling functionality such as checkpoint/restart. The one caveat to remember when using databases tied to hashes is not to use any hash operation that returns a list from the whole hash if the hash is very large, because that reads the list from the database into memory, thus defeating one of the purposes of tieing the hash to begin with. What this boils down to is, don't use the keys() or values() functions, nor the name of the hash itself, in a list context; use the each() function instead. Gaining this persistence with such a small change demonstrates the transparency afforded by tieing.
In a stunning display of extreme laziness, Damian Conway simplified the use of a rather arcane Perl feature, attributes (added in Perl 5.6.0), by writing a module Attribute::Handlers (merged into the core with Perl 5.8.0) that can be used to make an interface as simple as:
my $current_temp : Thermometer;
my $current_temp : Thermometer('Celsius');
When you have created an object class for which you can reasonably impute behavior that ought to happen when instances of that class undergo basic operations such as arithmetic (+ - * /), string (eq ""), or another type, you have the option of making it behave that way. This is the same kind of operator overloading that C++ has and Java doesn't.
Of course, the object class has to have semantics that are amenable to this sort of intervention. If you had a Date class, addition of two members of the class would not make sense; on the other hand subtracting one from another could reasonably be interpreted as yielding a member of a class encapsulating duration; and the overloaded addition of a Duration object and a Date object could be interpreted as yielding another Date object.
Overloading is most often used for providing transparent stringification; this allows you to implement "dwimmery" for your own objects. It's quite common for people to use an object in a string context either deliberately or by accident, and what they'll see printed ordinarily is something like "Foo=HASH(0x80fbb0c)". If you have defined an object class for which some kind of textual representation is natural, and you have found yourself implementing an instance method called, say as_text() or as_string(), then you can do your users a favor and overload stringification to call that method. This is as simple as inserting:
use overload '""' => \&as_text;
in your module. An example of a module that uses this to good effect is URI.pm (see Section 6.2.1), because the most common thing to want to do with a URI object is get at the textual form of the URI it represents. For more information, see the documentation for the overload pragma.
Yes, you can have tied variables that are also overloaded.
|Previous||Table of Contents||Next|