Hands off that constant

In this Stackoveflow question titled “How do I programatically construct constant name and use value of constant?”, we have someone who wants to be able to construct names of constants and look up their values.

A Perl programmer knows that if you want to look up values based on string keys, you should use a hash.

To make things concrete, let’s look at the OP’s code:

use constant {
    STATS_AXLE_SPOT_OK => 0,
    STATS_AXLE_SPOT_ERROR => 1,
    STATS_AXLE_SPOT_SKIPPED => 2,


    STATS_AXLE_FORWARD_OK => 3,
    STATS_AXLE_FORWARD_ERROR => 4,
    STATS_AXLE_FORWARD_SKIPPED => 5,
};

sub DoStuff {
    print eval "STATS_AXLE_$_[0]_$_[1]";
}

# prints value of constant STATS_AXLE_SPOT_SKIPPED
DoStuff("SPOT", "SKIPPED");

And, yes, it works. It works in the sense that when run, the code prints 2.

Is that good enough?

Constants defined by constant have one important use case: The compiler knows at compile time that they are constant. So, for example, if you have this:

use constant DEBUG => 0;
...
DEBUG
    and print_expensive_information();

the compiler can remove that chunk of code at compile time instead of checking the condition during run time.

However, constants defined using constant also have some significant disadvantages. For example, things get ugly if you want to interpolate them, you can’t easily enumerate them, and they are not private to your package.

A couple of years ago, Neil Bowers sat down, and wrote an excellent review of CPAN modules for defining constants. After reading that review, I did a few comparisons myself, and I was struck by how natural Const::Fast seemed to be.

So, let’s write this code in terms using Const::Fast:

#!/usr/bin/env perl

use strict;
use warnings;

use Const::Fast;

const my %STATS_AXLE => (
    SPOT_OK => 0,
    SPOT_ERROR => 1,
    SPOT_SKIPPED => 2,
    FORWARD_OK => 3,
    FORWARD_ERROR => 4,
    FORWARD_SKIPPED => 5,
);

sub DoStuff {
    print $STATS_AXLE{"$_[0]_$_[1]"};
}

# prints value of constant STATS_AXLE_SPOT_SKIPPED
DoStuff("SPOT", "SKIPPED");

This is clunkier than I would have liked because I am preserving the OP’s structure. I am not sure if a nested hash or two separate hashes would be more appropriate. Even then, though, right off the bat, the code has a few advantages.

First, there is no string eval. Second, I have now gained the ability to enumerate all %STATS_AXLE codes and their values, and to possibly do reverse lookups. Third, I can also pass this hash to subroutines using these values. That is, users of the lookup table do need not to rely on things in the package namespace.

Another recommendation to the OP was to use something like this:

use strict;
use Carp qw(croak);

...

sub lookup_const {
  my $name = shift;

  croak "No such constant '$name'" unless defined &$name;  # this works even under strict

  no strict 'refs';
  return &{$name};   # this won't work under strict
}

Or, you could inline this, but then you would have to pepper every place in your code where you do a look up in the package’s symbol table with additional no strict 'refs'; statements.

The check and `croak` might seem unnecessary, but it will show you where you tried to look up a non-existent value. If you let the return &{$name}; die, you won’t get that information. So, this method does add some overhead, but it has the benefit of providing a single point of mediation for the lookup of constants using their names which might have some future value.

Most importantly, being a proponent of writing what I mean, if I need constant values I can lookup using a string key, I would use Const::Fast to define the appropriate hash table.

Benchmarks

What good is an argument about programming without benchmarks? For this, I wrote three short scripts:

String eval version

#!/usr/bin/env perl

use strict;
use warnings;
use constant FLAG_ONE => 1;
use constant FLAG_TWO => 2;

my $buffer;
for my $i (1 .. 1_000_000) {
    $buffer .= eval "FLAG_" . [qw(ONE TWO)]->[$i % 2];
}

Symbol table lookup

#!/usr/bin/env perl

use strict;
use warnings;
use constant FLAG_ONE => 1;
use constant FLAG_TWO => 2;

my $buffer;
for my $i (1 .. 1_000_000) {
    no strict 'refs';
    $buffer .= lookup_const("FLAG_" . [qw(ONE TWO)]->[$i % 2]);
}

sub lookup_const {
    my $name = shift;

    die "No such constant '$name'" unless defined &$name;
    no strict 'refs';
    return &{$name};
}

Using Const::Fast

#!/usr/bin/env perl

use strict;
use warnings;
use Const::Fast;

const my %FLAG => (
    ONE => 1,
    TWO => 2,
);

my $buffer;
for my $i (1 .. 1_000_000) {
    $buffer .= $FLAG{ [qw(ONE TWO)]->[$i % 2] };
}

Let’s run those:

C:\...> timethis perl b-string-eval.pl
...
TimeThis :  Elapsed Time :  00:00:27.485

Yeah, don’t use string eval ;-)

C:\...> timethis perl b-symbol-table.pl
...
TimeThis :  Elapsed Time :  00:00:03.609

And, now, for Const::Fast:

C:\...> timethis perl b-const-fast.pl
...
TimeThis :  Elapsed Time :  00:00:01.234

It seems to me the results speak for themselves.

An objection

Of course, one might claim that the lookup_const subroutine is completely unnecessary, that an inlined symbol table lookup is much faster.

That is absolutely true.

If you are going to lookup a constant defined by constant using its name in only one place, definitely, just do that using:

no strict 'refs';
$name->();

in the smallest possible scope.

If you need key-value lookup for your constants, Const::Fast seems much more appropriate to me.