Chapter 6

Binding Variables to Objects


CONTENTS


This chapter is dedicated to describing how the tie() function works in Perl. The tie() function enables you to create variables that are tied to specific methods called when a variable is written to or read from. Using the tie() function, you can eliminate the need for calling methods designed specifically for certain types of variables.

The tie() Function

The tie() function is used to bind a variable to an object class. Here's the syntax:

tie ($variable,$classname,@list);

The tie() function binds a variable to a class that then provides the methods for the variable. The $variable has to be set to the name of the variable to be tied. The $classname is the name of the class implementing objects to which you want the variable tied. The @list is the list of arguments that may be required for this variable's class methods.

The object returned by the tie() function is just like the new() function for an object. You can use the returned value from the tie() function to access other methods in the class you just tied the object to.

Once you tie a variable to a class, the class's behavior is reflected in the way you access the variable. Each of the methods for the type of object is called when the type of action for the variable is seen by Perl. Three types of objects can be tied to classes: scalars, arrays, and associative arrays.

It's often necessary to disassociate an object from a class. This is done with the use of the untie() function. Simply call the untie($object) function and you're done. The untie() function works whether you are tied to a scalar, array, or hash object. The next three sections illustrate how to use tie() on each of these objects.

Tying Scalars

A class implementing a scalar object that can be tied to must implement these four methods:

Think of these methods as events for the class. When a variable is first tied to a class, the TIESCALAR method is called. Every time the tied variable is read from, the value from the FETch method is returned. When the tied variable is assigned to, the STORE method is called. Finally, when the tied variable loses scope, the DESTROY method is called.

Listing 6.1 contains a script that uses the tie() function for the Knot class.


Listing 6.1. Using the tie() function.
 1 #!/usr/bin/perl
 2
 3 push(@Inc,".");
 4 use Knot;
 5
 6 #
 7 # From now on the variable $currentTime
 8 # will behave as defined in functions in Knot.pm
 9 #
10 tie $currentTime, 'Knot';
11
12 #
13 # Test the FETch method
14 #
15 $x = $currentTime;
16 print " x= $x\n";
17 print " current = $currentTime\n";
18
19 #
20 # Test the STORE method
21 #
22 # In Knot.pm we have defined the $currentTime
23 # variable to behave as a readonly object.
24 # The following message will bail out with an error message.
25 #
26 $currentTime = $x;
27
28
29 #
30 # As soon as we drop out of the script here, the DESTROY
31 # method will be called on $currentTime.
32 #

Let's examine the code in Listing 6.1 to see where the methods for Knot.pm are invoked. In line 3, the address of the current directory is added to search for included modules. In line 4, the Knot.pm module is requested with the use statement. The Knot.pm file contains the module code for the class that allows variables to be tied.

In line 10, the variable $currentTime is tied to the class Knot. The TIESCALAR function in Knot.pm is called at this point. There are no additional arguments to be passed to the TIESCALAR function, so only two parameters, the variable number and the class name, are sent.

In line 15, the $currentTime variable is read from and the value of $currentTime is assigned to $x. Instead of treating $currentTime as a normal variable, Perl uses the FETch method of the tied class. The FETch method returns the current date in this example. You can write your own function. In line 17, the $currentTime variable is accessed again. This time, the FETch method is called again.

The program must not attempt to assign a value to the $currentTime variable. See line 26 in Listing 6.1. The Knot.pm module is implemented to allow only read-only variables; therefore, the FETch function will print an error message when the code at line 26 is executed.

Finally, the DESTROY method is called when the $currentTime variable is destroyed. The destruction is done automatically by Perl when the $currentTime variable goes out of scope. In this example, the DESTROY method simply prints an error message.

Here is the output from Listing 6.1.

 x= Sat Jun  1 12:54:25 CDT 1996

 current = Sat Jun  1 12:54:25 CDT 1996

Hey dude! We are making this readonly!
 at Knot.pm line 54
    Knot::STORE called at ./6_1.pl line 26

Knot::  unknotted!

Now let's look at the Knot.pm file in Listing 6.2.


Listing 6.2. The Knot.pm file.
 1 #!/usr/bin/perl
 2
 3 # ----------------------------------------------------------------
 4 # Sample file that shows how to tie variables to classes.
 5 #
 6 # This library is hereby placed in the public domain. Copy freely
 7 # as long as you give me credit for it!
 8 # Kamran Husain. khusain@ikra.com
 9 # ----------------------------------------------------------------
10 package Knot;
11 use Carp;
12 use strict;
13
14 #
15 # TIESCALAR classname, argument-list-here
16 #     This is the constructor for the class.  It returns a reference
17 #     to a new object for the class name.
18 #
19 sub TIESCALAR {
20         my $class = shift;
21         my $this = {};
22     #
23     # print "\n $class, $this";
24     #
25         return bless \$this, $class;
26         }
27
28 #
29 # FETch this
30 # The FETch method will be triggered every time the tied variable
31 # is accessed.
32 # The only argument to this function is the object itself.
33 # In this case, we just return the date.
34 #
35 sub FETch {
36           my $self = shift;
37           confess "wrong type" unless ref $self;
38           croak "usage error" if @_;
39           my $override;
40           $override = 'date';
41           return $override;
42           }
43
44 #
45 # STORE this, value
46 # This method will be triggered every time the tied variable is
47 # written to. It expects only two arguments: a reference to itself
48 # and a value that is being assigned.
49 #
50 sub STORE {
51              my $self = shift;
52              confess "wrong type" unless ref $self;
53              my $value = shift;
54          confess "Hey dude! We are making this readonly!\n";
55              return $value;
56          }
57
58 #
59 # DESTROY this
60 # This method will be triggered when the tied variable needs to be
61 # destructed. This method can be just empty for most classes since
62 # Perl's garbage collection will.
63 #
64
65 sub DESTROY {
66              my $self = shift;
67              confess "wrong type" unless ref $self;
68          print "\nKnot::  unknotted!\n";
69          }
70
71 #
72 # The obligatory ending true statement.
73 #
74 1;

Knot.pm defines the package in line 10 and imports the Carp and strict packages in lines 11 and 12, respectively. Line 74 terminates the module.

Lines 19 through 26 define the TIESCALAR function. The TIESCALAR function behaves a lot like the constructor of an object in Perl. It creates an associative array and returns a reference to this array after a call to the bless() function. (See Chapter 5, "Object-Oriented Programming in Perl," for more information on bless-ing objects.)

The FETch method starts at line 35. The FETch method is called every time the tied variable is read from. The only argument to the FETch method is a reference to the object itself. At line 37, the class type is confirmed, although it's not absolutely necessary to do this. Lines 39 through 41 return the current date and time for the value of the tied variable.

The STORE method is defined from line 50 through 56. In this case, we do not let values of the arguments that are passed in be assigned to anything because we want to make this value a read-only variable. You can easily modify this function to take some other action than what's shown in this example. The FETch method takes only two arguments: a reference to itself and a value that is being assigned. The confess() call is from within the Carp module.

The DESTROY method is called when the tied variable is destroyed. Normally, this function is empty. For this example, there is a print statement to show where the DESTROY function is called.

Tying to an Array

An array variable can be tied to a class in the same manner as a scalar can be tied to a class. The real difference is that the input parameters to the methods now need an index used to address a value in array. A class implementing an ordinary array must have these methods:

The FETch, DESTROY, and STORE methods have the same names as those for scalars. However, the name of the constructor is different-it's called TIEARRAY. Let's define a new array type called Cuboid, which has its first five indexes provide special functions. The first three indexes are written to as the height, width, and depth of a cuboid. The next two indexes contain the volume and surface area of the cuboid and are made read-only. The rest of the array can be made into a bounded array to allow a user to store his or her own values. As soon as a value is stored in the Cuboid array, the values of items at index 3 and 4 are recalculated to provide the latest volume and surface area of a cuboid.

Listing 6.3 illustrates how to use this array.


Listing 6.3. Using the Cuboid.pm module.
 1 #!/usr/bin/perl
 2
 3 push(@Inc,".");
 4 use Cuboid;
 5
 6 tie @myCube, 'Cuboid', 3;
 7
 8 $myCube[0] = 2;
 9 $myCube[1] = 3;
10 $myCube[2] = 4;
11
12 for ($i=0; $i < 5; $i++) {
13     print " myCube[$i] = $myCube[$i] \n";
14 }
15

Here is the output of this code.

array will be 8 elements long
[STORE 2 at 0]
[STORE 3 at 1]
[STORE 4 at 2]
 myCube[0] = 2
 myCube[1] = 3
 myCube[2] = 4
 myCube[3] = 24
 myCube[4] = 52

Now let's examine the Cuboid.pm module, which is presented in Listing 6.4.


Listing 6.4. The Cuboid.pm module.
 1 # ------------------------------------------------------------
 2 package Cuboid;
 3 use Carp;
 4 use strict;
 5
 6 #
 7 # The constructor for this class.
 8 # ------------------------------------------------------------
 9 # Array[0] = ht;           read write
10 # Array[1] = wd;           read write
11 # Array[2] = dp;           read write
12 # Array[3] = volume;       read only
13 # Array[4] = surfaceArea;  read only
14 # Array[5...maxsize] = read/write values for the user;
15 # ------------------------------------------------------------
16
17 my $SACRED = 5;
18
19 sub TIEARRAY {
20
21     my $class = shift;
22     my $maxsize = shift;
23
24     #
25     # Bailout if the array is not tied correctly.
26     #
27              confess "usage: tie(\@ary, 'Cuboid', maxsize)"
28                  if @_ || $maxsize =~ /\D/;
29          $maxsize += $SACRED;
30         print "array will be $maxsize elements long\n";
31              return bless {
32                  MAXSIZE => $maxsize,
33                  ARRAY => [0,0,0,0,0],
34              }, $class;
35          }
36
37 # FETch this, index
38 # This method will be triggered every time an individual element the tied
39 # array is accessed (read). It takes one argument beyond its self
40 # reference: the index whose value we're trying to fetch.
41 #
42 sub FETch {
43            my($self,$ndx) = @_;
44            if ($ndx > $self->{MAXSIZE}) {
45                  confess "Error Out of Bounds: $ndx > $self->{MAXSIZE}";
46                }
47            return $self->{ARRAY}[$ndx];
48          }
49
50 # STORE this, index, value
51 # This method will be called whenever an element in the tied array
52 # is written to. It takes three arguments: a reference to itself,
53 # the index to store stuff at, and the value to store at the index.
54 #
55 # The items at [3] and [4] are not allowed to be written to.
56 #
57 sub STORE {
58            my($self, $ndx, $value) = @_;
59            print "[STORE $value at $ndx]\n";
60            if ($ndx > $self->{MAXSIZE} ) {
61              confess "Error Out Of Bounds: $ndx > $self->{MAXSIZE}";
62            }
63        if (($ndx == 3) || ( $ndx == 4))  {
64              confess "Cannot store in read only area: $ndx";
65            }
66            $self->{ARRAY}[$ndx] = $value;
67           $self->{ARRAY}[3]  =
68         ($self->{ARRAY}[0] * $self->{ARRAY}[1] * $self->{ARRAY}[2]) ;
69           $self->{ARRAY}[4] =
70         ($self->{ARRAY}[0] * $self->{ARRAY}[1])  +
71         ($self->{ARRAY}[1] * $self->{ARRAY}[2])  +
72         ($self->{ARRAY}[0] * $self->{ARRAY}[2]);
73
74           $self->{ARRAY}[4] *=  2;
75
76            return $self->{ARRAY}[$ndx] ;
77          }
78
79 # DESTROY
80
81 sub DESTROY { }
82
83 1;

The Cuboid package is started at line 2 and ends at line 83 with a required statement. The package uses Carp and strict packages at lines 3 and 4, respectively. Lines 9 through 14 describe the structure of this object. The size of the sacrosanct indexes is set at line 17.

The TIEARRAY constructor starts at line 19. Note how the constructor takes two parameters: one for the class and one for the maximum size the array can take. Line 27 contains some error-checking routines using the confess() function in the Carp module. The maximum size is adjusted for the sacrosanct indexes, and an appropriate message is printed out at line 30.

A reference to the newly created object is returned in lines 31 through 34. Note how the array is initialized and the member MAXSIZE set at line 33.

The FETch method for the array behaves in the same manner as for a scalar. The FETch method is called every time an individual element in the tied array is read from. The FETch method takes two arguments: a reference to itself and the index whose value is being fetched. Look at line 43 where these two values are assigned to $self and $ndx. Bounds are checked at line 44, and we bail out in case of an error at line 45. If the bounds are correct, the value is returned at the request index via code at line 47.

The STORE method starts at line 50 and takes three arguments: a reference to itself, the index at which to store, and the value to store. In the STORE method, the extracted values are printed at lines 58 and 59. Bounds checking is done at line 60 with a bailout at line 61 in case of errors. Lines 63 through 65 do not permit storing values at index 3 or 4.

At line 66 the input value is assigned. At this point, you could make the code faster by checking whether only indexes 0 to 1 are modified, but you'll need to do that on your own. The area and volume results are stored at index 3 and 4. The assigned value is returned in line 76.

The DESTROY method is just a dummy function that doesn't do much. You really don't need to have a DESTROY method. The one shown here is for illustration only.

Caution
Only the read and write operations of an array are affected by the tie() operation. The functions $#, push(), pop(), and so on of an array are not affected by the tie() function.

Tying to an Associative Array

An associative array is more complicated to implement than either a scalar or an array because of the extra functions that have to be added to it. A class that can be tied to an associative array should have the following methods:

The next example is a simpler version of the one presented by Tom Christiansen in his perltie.html file, which is available on www.perl.com. The example presented by Tom is a bit dangerous to use because you can wipe out your .profile, .history, .elm, .term, and other "dot" files. I wrote this example to map the text versions of the perltie.html file into a hashed array to generate a table of contents for this book based on the first line of each heading. The example presented here simply lists the first line of each heading. There is no reason why you cannot print other information in the file, however.

Listing 6.5 shows the script that uses the tie() function on a hash. The module used for mirroring the contents of each chapter file is called Mirror.pm. It's used in line 4 of this code.


Listing 6.5. Using the tie() function on a hash.
 1 #!/usr/bin/perl
 2
 3 push(@Inc,".");
 4 use Mirror;
 5
 6 tie %chapters, 'Mirror', chaps;
 7
 8 foreach $k (keys %chapters) {
 9     print "$k is  $chapters{$k}";
10     }
11 print " End of script\n"

Here is sample input and output from the code in Listing 6.6.

$ test3
ch25.txt is  (a) Chapter 25
ch08.txt is  (a) Chapter 8
ch28.txt is  (a) Chapte 28
ch02.txt is  (a) Chapter 2
ch29.txt is  (a) Chapter 29
ch12.txt is  (a) Chapter 12
ch15.txt is  (a) Chapter 15
ch06.txt is  (a) Chapter 6

 All done!
 End of script
$

What you see above is the first line of every file listed in the hash instead of the filename! Had we not tied our own functions to the %chapters hash, we would be seeing the names of the file, such as ch29.txt, ch12.txt, and so forth. Instead of these names, when each element in the %chapters hash is accessed, our own function is called and prints out the first line in every file.

Of course, in your code, you would probably be using different functions to implement your own strategy and functions with a hash. For example, each access to a hash is tied to a record in a file. The function behind the access to each element in the hash would somehow take the record and format it to suit your needs. The example shown here in Listing 6.6 is simple enough to modify to fit into your own particular application.

Let's now look at how the code for Mirror.pm is written. By examining this code, you will be able to see how each function is defined for each type of action that you can tie a function to.


Listing 6.6. The Mirror.pm file.
  1 # This file is hereby put in the public domain. Copy freely.
  2 # Just give me some credit for it ;-) if you like. Kamran.
  3 package Mirror;
  4 use Carp;
  5
  6 # TIEHASH classname, $list
  7 #  This is the constructor for the class. That means it is expected to
  8 #  return a blessed reference of a new object.
  9 #
 10 sub TIEHASH {
 11              my $self = shift;
 12              my $dir  = shift || 'pwd';
 13              my $node = {
 14                  HOME    => $dir,
 15                  LIST    => {},
 16
 17              };
 18          #
 19          # print " Directory = $dir\n";
 20          #
 21              opendir(DIR, $dir) || croak "Cannot open $dir: $!";
 22              foreach $item ( grep /\.*txt/, readdir(DIR)) {
 23                  $node->{LIST}{$item} = 'head -1 $dir/$item';
 24          print "$node->{LIST}{$item} \n" if $debug;
 25              }
 26              closedir DIR;
 27              return bless $node, $self;
 28          }
 29
 30
 31 # FETch this, key
 32 # This method is called whenever an element in the tied hash is
 33 # being read. It takes two arguments: a reference to itself and the
 34 # key whose value is being asked.
 35
 36 sub FETch {
 37              my $self = shift;
 38          my $fname = shift;
 39              my $dir = $self->{HOME};
 40              my $file = "$dir/$fname";
 41              unless (exists $self->{LIST}->{$fname} || -f $file) {
 42                  carp "ERROR: no such file $fname ";
 43                  return undef;
 44              }
 45              if (defined $self->{LIST}->{$fname}) {
 46                  return $self->{LIST}->{$fname};
 47              } else {
 48                  return $self->{LIST}->{$fname} = 'head -1 $file';
 49              }
 50
 51          }
 52
 53
 54 # STORE this, key, value
 55 #     This method is called whenever an element in the hash is
 56 #     written to.  It takes three arguments: a reference to itself, the
 57 #     index to store at, and the value to store.
 58
 59 sub STORE {
 60              my $self = shift;  # this
 61              my $fname = shift;    # the key
 62              my $value = shift; # the value
 63              my $dir = $self->{HOME};
 64              my $file = "$dir/$fname";
 65          print "Storing $fname, $value $file \n";
 66         if ($value eq "done") {
 67             print "Storing $fname, $value $file \n";
 68                      return $self->{LIST}->{$fname} = 'head -1 $file';
 69             }
 70         else    {
 71                      return $self->{LIST}->{$fname} =  $value;
 72             }
 73          }
 74
 75 # DELETE this, key
 76 #
 77 #    This method is called when an item is deleted from the hash.
 78 #
 79 sub DELETE   {
 80
 81              my $self = shift;
 82              my $fname = shift;
 83              my $dir = $self->{HOME};
 84              my $file = "$dir/$fname";
 85              delete $self->{LIST}->{$fname};
 86          }
 87
 88 # CLEAR this
 89 #     This method is called when the whole hash is set to an empty list.
 90 #
 91 sub CLEAR    {
 92              my $self = shift;
 93              my $fname;
 94              foreach $fname ( keys %{$self->{LIST}}) {
 95                  $self->DELETE($fname);
 96              }
 97          }
 98
 99 #
100 # EXISTS this, key
101 #     This method is called when the exists() is called on a hash.
102 #
103 sub EXISTS   {
104              my $self = shift;
105              my $dir = $self->{HOME};
106              my $fname = shift;
107              my $file = "$dir/$fname";
108              return exists $self->{LIST}->{$file};
109          }
110
111 # FIRSTKEY this
112 #     This method is called when you start to iterate a list.
113 #
114 sub FIRSTKEY {
115              my $self = shift;
116              my $x  = keys %{$self->{LIST}};
117              each %{$self->{LIST}}
118          }
119
120
121 #
122 # NEXTKEY this, lastkey
123 #      This method is called during a keys() or each() iteration. The
124 # first argument is the object itself. The second argument is the last
125 # key that was accessed.
126
127 sub NEXTKEY  {
128              my $self = shift;
129              return each %{ $self->{LIST} }
130          }
131
132
133 #
134 # DESTROY  the infamous epitaph!
135 #
136 sub DESTROY  { print "\n All done!"; }
137
138 1;

The TIEHASH function definition begins at line 10. The constructor takes two values as arguments: the first is the name of the class, and the second is an optional directory to work in. If the second parameter is not specified, the current working directory is used. The $node hash is used to store two parameters: HOME for the working directory and LIST for the list of items in this hash.

At line 21, the required information is collected for the hash and is stored away in LIST at line 23. The debug statement at line 24 is a very valuable tool while debugging. At line 27, the class is blessed and the reference to the $node is returned.

The FETch method is called whenever an element in the tied hash is being read. It takes two arguments: a reference to itself and the key whose value is being asked for. See lines 37 and 38, where the two parameters are extracted. It would be prudent to add some error correction here lest we look at the wrong path-this is done at line 41. At line 45 the returned value as cached in the init stages is returned. Had a new item to the hash been added with an undefined value, the code at line 45 would assign it a value.

The assignment-handling function is the STORE function. This function is called whenever an element in the hash is written to. It takes three arguments: a reference to itself, the index at which to store, and the value to store. The three arguments are extracted at lines 60 through 62. The third argument may be null, in which case the head of the file in the HOME directory is used. (Look at lines 66 through 72.)

The DELETE function is called when an item is deleted from the hash. The function is defined at line 81. There are two arguments to this function: a reference to the object and the index to remove. The delete() function is called to remove the indexed item from the LIST hash at
line 85.

The CLEAR function is called when the whole hash is removed, possibly by assigning an empty list to it. (The tied array has no such callback!) There is only one argument to this function, and that is a reference to itself. The CLEAR function is set to call the DELETE function in this example (see line 95). This call saves some code, but we could have just as easily used the delete() function in the same manner as DELETE.

The EXISTS function is called to check whether an item exists in a hash (see line 103). There are two arguments to this function: a reference to the object and the index to remove. It simply re-creates the key and uses this key to return a value from within the LIST hash.

The FIRSTKEY and NEXTKEY methods are called when the each() and keys() methods are called. The FIRSTKEY method is called when you start to iterate a list. The NEXTKEY method gets called during a keys() or each() iteration. The first argument to NEXTKEY is a reference to the object itself. The second argument is the last that was accessed.

For the file in Listing 6.6, you should now be able to derive your own classes for mapping hashes to functions. The tie() function, when used with hashes, provides more flexibility in defining methods than what the tie() function for arrays provides. However, using the hash is more complex than the array function because you have to define more methods with the hashing method.

There is supposedly some relief, though, with the use of the TieHash module provided with the Perl distribution. The TieHash module has predefined methods for you to pick and choose whatever functions you want to implement, and the rest are defaulted. There is a man page for the module in the Perl distribution, but it did not provide much information on how to actually use the module. Perhaps we'll see more documentation on this tool in later releases.

For More Information

This chapter has provided only some basic information on the use of the tie() function. There is an excellent document, called perltie.html, by Tom Christiansen that is available at most of the Perl archive sites. The perltie.html document has more detailed information on how to use the tie() functions. The tie() function is also used in modules distributed with Perl. Two interesting modules to look at are the Config and DBM file modules, which show interesting uses of the tie() function. The DBM file modules provide detailed examples of mapping records to disk with the use of tie() functions on hashes.

Summary

This chapter has provided the basic information on how to use the tie() function to provide an association between a Perl variable and executable functions. Scalars, arrays, and hashes can be associated with methods having special names such as FETch, STORE, and so on. By designing classes that provide methods these names, you can provide extra functionality to map scalar, array, or hash objects to other objects, processes, or disk files.