ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
9. TieContents: Give me a wild tie brother, - Stoddard King, The Ties That Bind Normally, when you read or update a scalar, array, hash, or filehandle, Perl performs the appropriate operation on the corresponding internal data structure. Alternatively, you can use the tie keyword to bind the value (or variable) to a user-defined object, so that when you read from or write to that variable, Perl simply calls a specific method of the object it is tied to. In other words, while it provides the implementation for a "normal" variable, Perl expects a user-defined module to do so for a tied variable. Once a variable is tied, even accesses from the C API of the Perl library are delegated to the corresponding tied object. This approach may seem like syntactic sugar, but as you'll see from the examples in this chapter, it is the syntax that gives it its power: an ordinary variable can be made to invoke a user-defined function whenever a variable is manipulated, without the user's code changing or being necessarily aware of the subterfuge. The most common use of this technique is to tie a hash variable to a module that can manipulate DBM files, which are typically disk-based hash tables (they can also be BTrees). This technique allows you to make a hash value persistent and is capable of storing much more information than can fit into available memory, while giving the impression that you are manipulating an ordinary associative array. In the following pages, we will study how tie works with the various data types, and look at a few useful examples of this feature. 9.1 Tying ScalarsAt the most basic level, there are only four things you can do with a scalar. You can create it, get or set its value, and destroy it (by ending the scope or undef'ing it). tie allows you to supply a subroutine to be called back for each of these operations. The syntax of tie is as follows: tie The first parameter should be one of the four supported types described above. The second parameter is the name of a user-defined class. You are expected to have invoked use When this statement is executed, Perl checks the type of the variable (the first parameter). It then calls the method The names FETCH, STORE, and TIESCALAR are similar to AUTOLOAD and DESTROY in that they have a special significance for Perl only under appropriate circumstances. That is, a module can have a method called FETCH, which can be used normally like any other user-defined subroutine. But if you use tie, this method assumes a special meaning. Perl does not care about the exact data structure used for the object (whether you used a hash or ObjectTemplate). Table 9.1 shows a variable "$temperature" tied to an automatic temperature control system, which is represented by a Perl module called AC.pm.[1] An attempt to read $temperature's value is translated to a call to the temperature sensor, and an attempt to set its value translates to a command to the heating system to do what is needed.
As you can see, the AC module is an ordinary class with a constructor and three object methods (whose names happen to be special). Perl interacts with this module behind the scenes, providing the user with a much simpler interaction model. You can get the tied object as the return value of tie or invoke the tied function to get to it at any other time. Therefore the statement $temperature = 20; is identical to (tied $temperature)->STORE(20); The untie function restores the original value of the variable and also calls the object's DESTROY method. Perl does not constrain the object's module in any way other than to expect it to provide the methods we saw earlier. It can store whatever data it wants, can have other methods, and is perfectly usable even in a non-tie context. 9.1.1 Example: StopwatchLet us look at a simple example of a stopwatch using a tied scalar. When you store any value into it, it notes the current time (that is, it ignores the value). When you retrieve a value from it, it returns the amount of time elapsed since the last time a store was attempted on it. This is how it is used: use Stopwatch; tie $s, 'Stopwatch'; # $s is scalar transparently tied to a Stopwatch object. $s = 0; # Writing to it forces a reset sleep(10); # Sleep 10 seconds print "$s\n"; # Should print 10 The example might sometimes print 9 because of sleep's resolution. Example 9.1 shows how Stopwatch is implemented. Example 9.1: Stopwatch Implemented Using tiepackage Stopwatch; sub TIESCALAR { my ($pkg) = @_; my $obj = time(); # $obj stores the time at last reset. return (bless \$obj, $pkg); } sub FETCH { my ($r_obj) = @_; # Return the time elapsed since it was last reset return (time() - $$r_obj); } sub STORE { my ($r_obj, $val) = @_; # Ignore the value. Any write to it is seen as a reset return ($$r_obj = time()); } 1; TIESCALAR notes the current time and returns a reference to a blessed scalar (with the current time in it). As was mentioned earlier, you are under no obligation to provide a blessed scalar reference; Perl does not care whether the object is a scalar or an array or a complex data structure. The only requirement is that it be blessed into a module that supports the FETCH and STORE methods. In this case, FETCH computes the interval between the current time (as reported by time) and the last reset time. Incidentally, the time calculations in this module work at the granularity of only a second. If you want a finer granularity, you can use the Time::HiRes module available from CPAN, which gives microsecond resolution on Unix systems (gives access to the usleep and ualarm system calls). On Microsoft Windows systems, you can use the Win32::Timer call for millisecond-level timing. 9.1.1.1 tie works with anonymous valuesThe first argument to tie must boil down to a scalar, array, hash, or filehandle value ; it does not have to be a variable. The following code shows two valid examples of scalar ties: $r = \$s; tie $$r, 'Stopwatch'; # Indirect tie to $s @foo = (1, 2); tie $foo[1], 'Stopwatch'; As you can see, this facility works with the underlying values and is not associated with a variable name (unlike the trace facility in TCL). |