Sign In/My Account | View Cart  
advertisement


Listen Print Discuss

Symbol Table Manipulation

by Phil Crow
March 17, 2005

Having almost achieved the state of perfect laziness, one of my favorite modules is Class::DBI::mysql. It makes MySQL database tables seem like classes, and their rows like objects. This completely relieves me from using SQL in most cases. This article explains how Class::DBI::mysql carries out its magic. Instead of delving into the complexities of Class::DBI::mysql, I will use a simpler case study: Class::Colon.

Introduction

One of my favorite modules from CPAN is Class::DBI::mysql. With it, I can almost forget I'm working with a database. Here's an example:

#!/usr/bin/perl
use strict; use warnings;

package Users;

use base 'Class::DBI::mysql';

Users->set_db('Main', 'dbi:mysql:rt3', 'rt_user', 'rt_pass');
Users->set_up_table('Users');

package main;

my @column_names = qw( Name RealName );
print "@column_names\n";
print "-" x 30 . "\n";

my $user_iter = Users->retrieve_all();

while (my $row = $user_iter->next) {
    print $row->Name, " ", $row->RealName, "\n";
}

Except for the MySQL connection information, no trace of SQL or databases remains.

My purpose here is not really to introduce you to this beautiful module. Instead, I'll explain how to build façades like this. To do so, I'll work through another, simpler CPAN module called Class::Colon. It turns colon-delimited files into classes and their lines into objects. Here's an example from a checkbook application. This program computes the balance of an account on a user-supplied date or the end of time if the user doesn't supply one.

#!/usr/bin/perl
use strict; use warnings;

use Getopt::Std;
use Date;
use Class::Colon Trans => [ qw(
    status type date=Date amount desc category memo
) ];

our $opt_d;
getopt('d');
my $date       = Date->new($opt_d) if $opt_d;

my $account    = shift or die "usage: $0 [-d date] account_file\n";
my $trans_list = Trans->READ_FILE($account);
my $balance    = 0;

foreach my $trans (@$trans_list) {
    if (not defined $date or $date >= $trans->date) {
        $balance += $trans->amount;
    }
}

print "balance = $balance\n";

In the use statement for Class::Colon, I told it the name of the class to build (Trans), followed by a list of fields in the order they appear in the file. The date field is really an object itself, so I used =Date after the field name. This told Class::Colon that a class named Date will handle the date field. If the Date class constructor were not named new, I would have written date=Date=constructor_name. My Date class is primitive at best, it only provides comparisons like greater than. It only does that for dates in one format. I won't embarrass myself further by showing it.

After shifting in the name of the account file, the code calls READ_FILE through Trans, which Class::Colon defined. This returns a list of Trans objects. The fields in these objects are the ones given in the Class::Colon use statement. They are easy to access through their named subroutines.

The rest of the program loops through the transactions list checking dates. If the user didn't give a date, or the current transaction happened before the user's date, the program adds that amount to the total. Finally, it reports the balance.

Though the example shows only the lookup access, you can easily change values. All of the accessors retrieve and store. Calling WRITE_FILE puts the updated records back onto the disk.

Other methods help with colon-delimited records. Some let you work with handles instead of file names. Others help you parse and produce strings so that you can drive your own input and output. See the Class::Colon perldoc for details. (No, colon is not the only delimiter.)

Let the Games Begin

Both Class::DBI::mysql and Class::Colon build classes at run time which look like any other classes. How do they do this? They manipulate symbol tables directly. To see what this means, I want to start small. Suppose I have a variable name like:

my $extremely_long_variable_indicator_string;

That's not something I want to type often. I could make an alias in two steps like this:

our $elvis;

First, I declare an identifier with a better name. I must make it global. If strict is on, I should use our to do this (though there are other older ways that also work). Lexical variables (the ones declared with my) don't live in symbol tables, so the tricks below won't work with them.

*elvis = \$extremely_long_variable_indicator_string;

Now I can point $elvis to the longer version. The key is the * sigil. It refers to the symbol table entry for elvis (the name without any sigils). This line stores a reference to $extremely_long_variable_indicator_string in the symbol table under $elvis, but it doesn't affect other entries like @elvis or %elvis. Now, both scalars point to the same data, so $elvis is a genuine alias for the longer name. It is not just a copy.

Unless you work with mean-spirited colleagues, or are into self-destructive behavior, you probably don't need an alias just to gain a shorter name. However, the technique works in other situations you might actually encounter. In particular, it is the basis for the API simplification provided by Class::Colon.

To understand what Class::Colon does, remember that the subroutine is a primitive type in Perl. You can store subs just as you do variables. For instance, I could store a subroutine reference like this (the sigil for subs is &):

my $abbr;
$abbr = \&some_long_sub_name;

and use it to call the subroutine:

my @answer = $abbr->();

Here, I have made a new scalar variable, $abbr, which holds a reference to the subroutine. This is not quite the same as directly manipulating the symbol table, but you can do that too:

*alias     = \&some_long_sub_name;
my @retval = alias();

Instead of storing a reference to the subroutine in a variable, this code stores the subroutine in the symbol table itself. This means that subsequent code can access the subroutine as if it had declared the subroutine with its new name itself. Adjusting the symbol table is not really easier to read or write than storing a reference, but, in modules like Class::Colon, symbol table changes are the essential step to simplifying the caller's API.

Pages: 1, 2

Next Pagearrow