Hey everybody. Sorry It's been so long since an update. I've had Oblivion taking up all my time of late. It's a great game; check it out if you get the chance—but that is really neither here nor there. This post is about perl.
Soon I'll be moving to a new position at work. I'm going to be taking a break from system administration for a bit and doing some coding, which is really a breath of fresh air. You know, it's the whole problem of dealing with users: they drive you crazy, but they also provide you with a job.
Anyway, the new projects I'll be working on will be web stuff, mostly in perl. So, I figured I'd lay off the ruby for a while and get back on the camel. I started out looking at a simple script to do ldap lookups for co-workers. We do have the capability to look up phone extentions using our VoIP enabled telephone sets, but it can really be a pain to key in the information.
It all comes down to the interface: I will never be able to key in a query against somebodies last name on the phone as fast as I can on the keyboard—even if I could remember which letter mapped to which number, you are still required to wait for a timeout between two characters. Otherwise, there would be no way to differentiate between entering 22 meaning "aa" or 22 meaning "b".
So the script is really simple, it just takes any number of query strings as command line arguments and plugs them into the ldap query filter using the Net::LDAP module. As it stands, everything worked fine on my workstation because I have a whole slew of useful perl modules installed there from the CPAN. Unfortunately, the other machines are missing a lot of these modules. I could have installed the missing modules locally on each machine, but that would have taken more effort than it would be worth.
Rather than go at it that way, I decided to just work around the problem. The missing functions were some of the really nice list processing routines from List::MoreUtils. We'll look at the function all() as an example. all() takes a reference to a function and a list of values. It uses the function as a predicate, returning true only when the function is true for each value in the list. Here is an example of it's usage:
if (all { $_ >= 0 } (1,5,4,0,3)) {
print "The list contains all positive values.\n";
}
else {
print "The list has at least one negative value.\n";
}
which of course would execute the first branch because they are all positive. It turns out that this function is pretty simple to write in perl, but there are some nice points to using the module version. First the standard reason, you don't have to reinvent the wheel. There's less chance that the module version has bugs in it then our hand rolled version. Second, the module version is written as a C extention, so it's really fast.
In the script we want to load the module if it is available, but if we do that, perl will not be able to find it in the include path (@INC) and the program will blow up. What we need to do is require the module file instead. require will throw an execption if the module is not found, and it also does not call the modules import function—we're going to want to do the import manually anyway as you'll see in a moment.
To catch the error, you can use an eval block. If an exception is thrown, it will get stuffed into the package variable $@. We can test this variable to see if the module couldn't be loaded. If not, we define the function ourselves. Otherwise, we just import the modules version into our current namespace. This looks something like the following:
BEGIN {
no strict 'refs';
eval { require List::MoreUtils };
if ($@) {
*all = sub (&@) {
my $func_ref = shift;
my @items = @_;
foreach (@items) {
return unless $func_ref->();
}
return 1;
}
}
else { *all = \&List::MoreUtils::all }
}
We put this inside of a BEGIN block because we want this to happen at compile time rather than runtime. The (&@) part after the sub definition is a function prototype. It tells the compiler that we want to look for a subroutine reference as the first parameter, which is what gives us a calling interface like the builtins grep and map.
The only other tricky part in there is the assignment to the all typeglob (*all). Since we are setting the value to a funciton reference, the all() function in the current namespace gets set to that function. Now when we call all() in the program we should get either the module version or the one we defined depending on whether it could find the module. If I run it on my workstation it uses the List::MoreUtils version. On the other machines, however, I don't have to worry about it not being available. The script will still run, It will just use our above definition instead.

0 comments:
Post a Comment