Reputation: 149
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
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
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
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