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:

  63 63 63 63 63 63

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