Dad,
Here's another neat example of Perl's ability to let you write simple and
direct code (if you know the tricks).
I've got a class which implements a tied hash (this is just Perlspeak for
overloading the { } hash-lookup operator). If you overload this, then Perl
expects you to provide a few pre-named functions. One of these, a
contructor, must be named TIEHASH. Here's how I originally wrote the
TIEHASH function for the PDB::File::Temporal class (a subclass of the
PDB::File::Momental class):
1 sub TIEHASH ($$$;) { 2 my ($class, $filename, $time, %etc) = @_; 3 my $self = SUPER::TIEHASH($class, $filename); 4 $self->{TIME} = $time; 5 my ($key, $val); 6 while (defined ($key,$val = each %etc)) 7 { $self->{$key} = $val; } }
|
|
The first line says that it takes at least 3 arguments. The next line says
they should be named $class, $filename, $time, and any extra stuff beyond
those three should be shoveled off into a local hash called %etc. (The %
character is a great symbol for a hash because the circle on the left of the
slash, I think of as a key and the circle on the right of the slash, I think
of as a value.) The third line calls a superclass constructor to set up a
basic Momental file. On the fourth line it adds the TIME attribute to the
returned object, and on the fifth through seventh lines it loops through the
%etc hash by key/value pairs and stuffs them away into $self. The reason for
the %etc hash is so that anyone subclassing this Temporal class can simply
pass in additional attributes without having to store them explicitly as we
do here.
An alternate formation of this routine is to use a hash "slice" to store
multiple values at once, as shown on the fifth line below:
1 sub TIEHASH ($$$;) { 2 my ($class, $filename, $time, %etc) = @_; 3 my $self = SUPER::TIEHASH($class, $filename); 4 $self->{TIME} = $time; 5 @self->{keys %etc} = values %etc; }
|
|
But since we're allowing subclasses of this class to simply pass a list of
additional attributes, why don't we do the same here and pass the buck up
the class hierarchy? Doing that makes it come down to this:
1 sub TIEHASH ($$$;) { 2 my ($class, $filename, $time, @etc) = @_; 3 return SUPER::TIEHASH($class, $filename, 'TIME'=>$time, @etc); }
|
|
The third line above now does everything that the other lines were doing,
provided of course that the superclass of this class knows how to handle
extra arguments. Note the use of "=>" in the time parameters. Recall
that "=>" is just syntactical synonym for ",", so that line is syntactically
identical to this line:
3 return SUPER::TIEHASH($class, $filename, 'TIME', $time, @etc);
|
|
Perl is smart enough to pair up even-odd values in a list when it's converting
them to a hash inside the receiving function. All the receiving function has
to do is use %foo instead of @foo to get Perl to do that. The callers of
course have to make sure that the even/odd-ness is started off correctly,
otherwise the receiving function will get all confused. (And the receiving
function has to expect the hash or list to be the very last argument, because
the first hash or list argument soaks up all the remaining arguments. You
can always pass things by reference if you need to pass multiple lists or
hashes, but that's a whole 'nother subject.)
Now what if the @etc list we're forwarding on is really long? That may cause
a lot of extra copying around in memory that may be noticeable if the
constructor is a bottleneck. So instead of plucking the arguments out of @_
with a list assignment (as in the second line above), let's shift them out of
@_ as in the second through fourth lines below, and pass the remainder of @_
to the superclass constructor, as in the fifth line below:
1 sub TIEHASH ($$$;) { 2 my $class = shift; 3 my $filename = shift; 4 my $time = shift; 5 return SUPER::TIEHASH($class, $filename, 'TIME'=>$time, @_); }
|
|
That avoids the use of a temporary variable @etc. (By the way, note that the
default argument to shift is @_ if none is specified. So you can just write a
plain shift when passing arguments (or anywhere else). Almost all built-in
functions and/or operators assume default arguments of @_ or $_ when not
specified.)
But now here's the coolest part...
Since Perl guarantees left-to-right argument evaluation (in cases like this),
lines 2 through 5 above can be written on a single line!
1 sub TIEHASH ($$$;) { 2 return SUPER::TIEHASH(shift, shift, 'TIME'=>shift, @_); }
|
|
That's about as simple, direct, and (arguably) readable as you can get.
Pretty neat, huh?
Unfortunately, Perl doesn't guarantee left-to-right evaluation in all cases.
For example, this:
sub foo { print "@_\n"; } foo(($a+=1), ($a+=2), ($a+=4), ($a+=8), ($a+=16), ($a+=32));
|
|
prints this:
And this:
sub foo { print "@_\n"; } foo($a++, $a++, $a++, $a++, '--', ++$a, ++$a, ++$a, ++$a, ++$a, ++$a, '--', --$a, --$a, --$a, --$a, --$a, --$a);
|
|
prints this:
0 1 2 3 -- 4 4 4 4 4 4 -- 4 4 4 4 4 4
|
|
So it is dangerous in general to rely on pure left-to-right evaluation in
argument passing. The fact that ++$a and $a++ have different
argument-passing semantics seems especially odd. What happens internally is that
perl makes two separate left-to-right passes over the arguments before
passing them: one pass reduces things to a simple form (this includes
function calls and pre-increment and pre-decrement operations) and the
second pass sets up the list, doing any remaining processing such as post-increment
and post-decrement operations.
--Todd