Perl Design Patterns
by Phil Crow
|
Pages: 1, 2
Decorating Lists
One of the most common tasks in Perl is to transform a list in some way. Perhaps you need to skip all entries in the list that start with underscore. Perhaps you need to sort or reverse the list. Many built-in functions are list filters. They take a list, do something to it and return a resultant list. This is similar to Unix filters, which expect lines of data on standard input, which they manipulate in some way, before sending the result to standard output. Just as in Unix, Perl list filters can be chained together. For example, suppose you want a list of all subdirectories of the current directory in reverse alphabetical order. Here's one possible solution.
1 #!/usr/bin/perl
2 use strict; use warnings;
3
4 opendir DIR, ".",
5 or die "Can't read this directory, how did you get here?\n";
6 my @files = reverse sort map { -d $_ ? $_ : () } readdir DIR;
7 closedir DIR;
8 print "@files\n";
Perl 6 will introduce a more meaningful notation for these operations,
but you can learn to read them in Perl 5, with a little effort. Line 6
is the interesting one. Start reading it on the right (this is backward
for Unix people). First, it reads the directory. Since map expects
a list, readdir returns a list of all files in the directory. map
generates a list with the name of each file which is a directory (or
undef if the -d test fails). sort puts the list in
ASCII-betical order. reverse reverses that. The result is stored in
@files for later printing.
You can make your own list filter quite easily. Suppose you wanted to
replace the ugly map usage above (I tend to think map is always ugly)
with a special purpose function, here's how:
#!/usr/bin/perl
use strict; use warnings;
sub dirs_only (@) {
my @retval;
foreach my $entry (@_) {
push @retval, $entry if (-d $entry);
}
return @retval;
}
opendir DIR, "."
or die "Can't read this directory, how did you get here?\n";
my @files = reverse sort { lc($a) cmp lc($b) } dirs_only readdir DIR;
closedir DIR;
local $" = ";";
print "@files\n";
The new dirs_only routine replaces map above, leaving out the
entries we don't want to see.
The sort now has an explicit comparison subroutine. This is to prevent it
from thinking that dirs_only is its comparison routine. Since I had to
include this, I chose to take advantage of the situation and sort with
more finesse: ignoring case.
You can make such list filters to your heart's content.
I have now shown you the most important types of decoration. Any others you need could be implemented in the traditional GoF way.
The next pattern feels like cheating, but then Perl often gives me that feeling.
Flyweight
The idea of reusing objects is the essence of the flyweight pattern. Thanks to Mark-Jason Dominus, Perl takes this far beyond what the GoF had in mind. Further, he did the work once and for all. Larry Wall likes this idea so much he's promoting it to the core for Perl 6 (there's that promotion concept again).
What I want is this:
For objects whose instances don't matter (they are constants or random), those requesting a new object should be given the same one they already received whenever possible.
This pattern fails dramatically if separate instances matter. But if they don't, then it would save time and memory.
Here's an example of how this works in Perl. Suppose I want to provide a die class for games like Monopoly or Craps. My die class might look like this: (Warning: This example is contrived to show you the technique.)
package CheapDie;
use strict; use warnings;
use Memoize;
memoize('new');
sub new {
my $class = shift;
my $sides = shift;
return bless \$sides, $class;
}
sub roll {
my $sides = shift;
my $random_number = rand;
return int ($random_number * $sides) + 1;
}
1;
On first glance, this looks like many other classes. It has a constructor called new. The constructor stores the received number of sides into a subroutine lexical variable (a.k.a. a my variable), returning a blessed reference to it. The roll method calculates a random number, scales it according to the number of sides, and returns the result.
The only thing strange here are these two lines:
use Memoize;
memoize('new');
These exploit Perl's magic extraordinarily well. The memoize
function modifies the calling package's symbol table so that new is
wrapped. The wrapping function examines the incoming arguments (the
number of sides in this case). If it has not seen those arguments before,
then it would call the function as the user intended, storing the result in
a cache and returning it to the user. This takes more time and memory than
if I had not used the module.
The savings come when the method is called again. When the wrapper notices a call with the same arguments it used before, it does not call the method. Rather, it sends the cached object instead. We don't have to do anything special as a caller or as an object implementor. If your object is big, or slow to construct, then this technique would save you time and memory. In my case, it wastes both since the objects are so small.
The only thing to keep in mind is that some methods don't benefit from
this technique. For example, if I memoize roll, then it would return
the same number each time, which is not exactly the desired result.
Note too that Memoize can be used in non-object situations - in fact the documentation for it doesn't seem to contemplate using it for object factories.
Not only do languages such as Java not have core functions for caching method returns, they don't allow clever users to implement them. Mark-Jason Dominus did a fine thing implementing Memoize, but Larry Wall did a better thing by letting him. Imagine Java letting a user write a class that manipulated the caller's symbol table at run time - I can almost hear the screams of terror. Of course, these techniques can be abused, but precluding them is a greater loss than rejecting poor code on the few occasions that some less-than-stellar programmer improperly adjusts the symbol table.
In Perl all things are legal, but some are best left to modules with
strong development communities. This allows regular users to take advantage
of magic manipulations without worrying about whether our own magic
will work. Memoize is an example. Instead of rolling your own wrapped
call and caching scheme, use the well-tested one that ships with Perl
(and looked for the 'is cached' trait to do this for routines in Perl 6).
The next pattern is related to this one, so you can use flyweight to implement it.
Singleton
In the flyweight pattern, we saw that there are sometimes resources that everyone can share. GoF calls the special case when there is a single resource that everyone needs to share the singleton pattern. Perhaps the resource is a hash of configuration parameters. Everyone should be able to look there, but it should only be built on startup (and possibly rebuilt on some signal).
In most cases, you could just use Memoize. That seems most reasonable to me.
(See the flyweight section above.) In that case, everyone who wants access
to the resource calls the constructor. The first person to do so causes
the construction to happen and receives the object. Subsequent people call
the constructor, but they receive the originally constructed object.
There are many other ways to achieve this same effect. For instance, if
you think your callers might pass you unexpected arguments, then Memoize would
make multiple instances, one for each set of arguments. In this case,
managing the singleton with modules like Cache::FastMemoryCache from CPAN
may make more sense. You could even
use a file lexical, assigning it a value in a BEGIN block. Remember
bless doesn't have to be used in a method. You could say:
package Name;
my $singleton;
BEGIN {
$singleton = {
attribute => 'value',
another => 'something',
};
bless $singleton, "Name";
}
sub new {
my $class = shift;
return $singleton;
}
This avoids some of the overhead of Memoize and shows what I'm doing more directly. I made no attempt to take subclassing into account here. Maybe I should, but the pattern says a singleton should belong always to one class. The fundamental statement about singletons is:
``There can only be one singleton.''
Summary
All four of the patterns shown in this article use built-in features,
or standard modules. The iterator is implemented with foreach. The
decorator is implemented for I/O with Unix pipe and redirection
syntax or with a tied file handle. For lists, decorators are just
functions which take and return lists. So, I might call decorators
filters. Flyweights are shared objects easily implemented with the
Memoize module. Singletons can be implemented as flyweights or with
simple object techniques.
The next time some uppity OO programmer starts going on about patterns, rest assured, you know how to use them. In fact, they are built-in to the core of your language (at least if you have the sense to use Perl).
Next time, I will look at patterns which rely on code references or data containers.
Acknowledgements and Background
I wrote these articles after taking a training course using GoF from a well-known training and consulting company. My writing is also informed by many people in the Perl community, including Mark-Jason Dominus, who showed at YAPC 2002, using his unique flair, how Perl deals with the iterator pattern. Though the writing here is mine, the inspiration comes from Dominus and many others in the Perl community, most of all Larry Wall, who have incorporated patterns into the heart of Perl during the years. As these patterns show, time and time again, Perl employs the principle of promotion carefully and well. Instead of adding a collection framework in source code modules, as Java and C++ do, Perl has only two collections: arrays and hashes. Both are core to the language. I think Perl's greatest strength is the community's choices of what to include in the core, what to ship along with the core, and what to leave out. Perl 6 will only make Perl more competitive in the war of language design ideas.

