arkyc
arkyc

Reputation: 149

How do I programatically construct constant name and use value of constant?

I've done a search and couldn't find an answer for this. Apologies if it's been answered somewhere else.

I have a set of constants like this:

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,

}

What I would like to do is have a function where I can constuct the name of the constant and use the value of the constant.

eg.

sub DoStuff()
{
  my $l_deal_type = $_[0];
  my $l_status = $_[1];

  #code from here isn't correct.
  my $l_constant_name = "STATS_AXLE_" . $l_deal_type . "_" . $l_status;

  print $l_constant_name;


}

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

Thanks for your help!

Upvotes: 4

Views: 719

Answers (3)

Hynek -Pichi- Vychodil
Hynek -Pichi- Vychodil

Reputation: 26131

If you look at constant.pm you will find that constant is just a module which installs a function returning the constant value into the current namespace. It is done at the compile time and the Perl then optimise it as constant in following code. The key is that there is still the function so you can call it.

sub DoStuff
{
  my $l_deal_type = $_[0];
  my $l_status = $_[1];

  my $l_constant_name = "STATS_AXLE_${l_deal_type}_${l_status}";

  no strict 'refs';    
  print $l_constant_name->();
}

If you are desperate you can combine constant with Const::Fast and make this weird stuff:

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,
);
use constant STATS_AXLE => \%STATS_AXLE;

use v5.10;
for my $type (qw(SPOT FORWARD)) {
  for my $status (qw(OK ERROR SKIPPED)) {
    say "1st way STATS_AXLE_${type}_$status => ", $STATS_AXLE{"${type}_$status"};
    say "2nd way STATS_AXLE_${type}_$status => ", STATS_AXLE->{"${type}_$status"}; 
  }
}
# this works as well
say $STATS_AXLE{FORWARD_ERROR};
say STATS_AXLE->{FORWARD_ERROR};

Upvotes: 1

pilcrow
pilcrow

Reputation: 58731

Perl's constant effectively defines subroutines, and you can check for the existence of subroutines of a certain name in the symbol table ... symbolically:

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
}

EDIT:

However, you might not want to do this.

The typical, simple scalar constant (e.g., use constant FLAG_FOO => 1) is very good for the very limited application of defining inlinable subroutines that give the developer a meaningful name for an otherwise "magic" literal. This is not unlike a #define FLAG_FOO 1 in another language you might be familiar with.

When you go beyond this simple usage, the implementation of constant will chafe very quickly. Dynamic constant lookups by name defeat the inlining of the constant subs. Interpolating a "constant" is much easier with a read-only (by convention or otherwise) variable. Etc.

Take a look at other suggestions.

Upvotes: 2

Sinan Ünür
Sinan Ünür

Reputation: 118166

A brief summary: First, don't use string eval for this. It harms performance, and can mask problems with your code.

Second, the main question is why you need to do this. If you need a key-value lookup facility, Perl already has a data structure for that, and you should use it.

If you only need to do this in one place, invoking the constants created with your use constant statement as functions by constructing their names is OK. That's what Hynek -Pichi- Vychodil's answer is doing.

If you need to do this in multiple places in your code, you'll be better off mediating the lookups so your code is not littered with no strict 'refs'. That's what pilcrow's answer gives you. The check for the definedness of the constant subroutine hurts performance, but is necessary if you want the exception to tell you what part of your code tried to look up a non-existent value.

To me, it looks like Const::Fast is more appropriate for your situation. It allows you to collect related constants in a single namespace that is not your package namespace, look up and interpolate those constants using simple Perl constructs etc.

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,
);

Then you can do say $STATS_AXLE{SPOT_ERROR} or

say $STATS_AXLE{ "${l_deal_type}_${l_status}" };

or

say $STATS_AXLE{ "$_[0]_$_[1]" };

is your DoStuff routine.

This will croak if the key does not exist in %STATS_AXLE.

For an excellent comparison of CPAN modules for defining constants, please see Neil Bowers' excellent review. His recommendation, which I agree with, is:

If you want array or hash constants, or immutable rich data structures, use Const::Fast. It's a close race between that and Attribute::Constant, but Const::Fast seems maturer, and has had more releases.

PS: Note that sub DoStuff() declares DoStuff as not taking any arguments. Just don't use Perl's prototypes. They don't do what most people expect them to do. Do:

sub DoStuff {
    ....
}

Upvotes: 3

Related Questions