2003-12-27

Comma Chameleon

This Perl fragment does not do what you might think:

  use constant foo => 'BAR';
  my $hashref = {
    foo => 43
  };
  printf "%s: %s\n", foo, $hashref->{foo};

That is, it prints "BAR: 43", but not for the reason you might think.

The above code is equivalent to this:

  my $hashref = {
    'foo' => 43
  };
  printf "%s: %s\n", 'BAR', $hashref->{'foo'};

Not to this:

  my $hashref = {
    'BAR' => 43
  };
  printf "%s: %s\n", 'BAR', $hashref->{'BAR'};

You can see this by tacking the following on to the end of the original code:

  use Data::Dumper;
  print Dumper($hashref);

in which case you will see this output:

  BAR: 43
  $VAR1 = {
    'foo' => 43
  };

The reason for this has to do with the way constants are implemented in Perl; they are not implemented in the core, but by a module that involves some trickery.

The first clue that something is odd is that 'constants' are introduced by some odd module-using syntax: "use constant foo => 'BAR'" instead of something that looks more like a regular variable declaration (such as "my constant $foo = 'BAR'" — NOT legal Perl syntax). That "fat comma" ("=>") is the same one used in constructing hashes, and provides an extra bit of context to the token on its left. Note that there were no quotes around foo, meaning it is treated as a "bareword" (a literal string without quotes) if used in combination with the fat comma.

So, the line that introduces the new constant is equivalent to this:

  use constant 'foo', 'BAR';

The second clue that something odd is going on is that when you 'use constant', you don't refer to your 'constants' like normal variables. You use a bareword form ('foo') rather than a variable form ('$foo'). That is exactly the sort of syntax you can use when calling a subroutine without any arguments, and it turns out that 'use constant' is creating a subroutine with the given name behind the scenes!

If you read the documentation (perldoc constant), you will see the technical note abut the implementation using inlinable constant subroutines.

The technique is quite simple, really. When you say:

  use constant foo => 'BAR';

It is really equivalent to this:

  sub foo { 'BAR' }

And that is why you refer to you constant this way (as a subroutine call):

  print foo, "\n";

instead of this way (as a variable):

  print "$foo\n";

The use constant module in the Perl library is a neat hack, but odd enough that I rarely use it. If I'm working with someone else's code that uses it, I play along, but generally avoid it whenever possible. I'm holding out for real Perl constants.

In the mean time, there is another way to get package-global constants that work more like variables. Its not without its own issues, but here is a simple self-contained example script demonstrating the technique:

  use vars qw($foo);
  *foo = \43;
  print "$foo\n";
  $foo++;

If you run this program you will get the output:

  43
  Modification of a read-only value attempted at - line 4.

That "use vars qw($foo);" line is the old-school way of declaring a package-global variable. A more modern way of saying the same thing would be:

  our $foo;
  *foo = \43;
  print "$foo\n";
  $foo++;

(The only difference being the "our $foo;" line.)

Finally, you can make this prettier by creating a module const.pm like this:

  package const;
  sub import { *{scalar(caller) . '::' . $_[1]} = eval "\\$_[2]"; }
  1;

And then you can create (scalar) constants with a syntax similar to the constant standard Perl library module, but with the ability to use variable-style syntax to access them:

  use strict;
  use const foo => 43;
  print "$foo\n";
  $foo++;

No comments: