4

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!

5
  • All good I think and can be solved by using the eval function. eg. replace print $l_constant_name with print eval($l_constant_name); Open to suggestions on a better solution though. Commented Mar 5, 2015 at 0:37
  • 2
    Using eval to get dynamic constants sounds like a really, really bad idea. I believe in 99.9999% of the time I would just use a regular variable instead. Never quite got the point of constants, its a variable that you should not change, virtually indistinguishable from a variable that you never change. Commented Mar 5, 2015 at 4:31
  • @TLP, one point is that Perl's optimizer does constant folding, but it can't do that on variables (even if you set it once and never change it). Constant subroutines, however, can be folded. Commented Mar 5, 2015 at 7:43
  • 1
    @cjm Except that once one tries to call those subroutines symbolically one throws out whatever performance advantage constant might have over Const::Fast. Commented Mar 5, 2015 at 11:02
  • @cjm Ah, yes, I remember that now, from some course 20 years ago. :) That seems like a very small optimization these days, though. Commented Mar 5, 2015 at 15:36

3 Answers 3

3

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 {
    ....
}
Sign up to request clarification or add additional context in comments.

6 Comments

I don't think it's good idea to solve each problem by installing modules when the problem can be solved using just what people has already got.
@Hynek-Pichi-Vychodil The OP is using the package symbol table as a hash. I am showing a way to group similar constants so that they are also interpolable without having to resort to no strict. Also, this way the OP can lookup the name of a STATS_AXLE code by its value.
This module is in version 0.014 and is not involved in base Perl modules. Introducing such module for problem solvable by lest than 20 keystrokes is very bad idea.
The trade-off depends on how many places you are going to have to type those same 20 keystrokes.
BTW Const::Fast passed smell test: If there is not Debian package for it, you should not spend time with this weirdo. So OK :-)
|
2

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.

4 Comments

@SinanÜnür, I agree and have clarified. I do think the OP has a bit of an XY problem here — I answered his X and you showed him a better Y (and so I had upvoted you accordingly).
And I voted you up. Note that your check & croak might be considered unnecessary since &{$name} will die if $name is not the name of a defined sub. Of course, the croak still has a value (otherwise, you wouldn't know what part of your code called lookup.
I don't think constant.pm creates subroutines, but constants do behave as subs.
@ikegami I had never realized Devel::Symdump does not recognize them as functions, but lists them among $sym->ios. Good catch.
1

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};

6 Comments

@SinanÜnür: Once you access it dynamically it is not inlined as well as $STATS_AXLE{ "${l_deal_type}_${l_status}" } in your case. So what's the point? You are nagging about as same flaw as in your code. LOL
He had one problem. You proposed to install a module, now he have two problems. I agree that he should use a hash instead of the symbol table, but your solution makes it worse. I have experience of developing and maintaining of hundreds of thousand lines of Perl code-base which supposed to run 24/7 as a server and involving another module for solving something solvable in less than 20 keyboard strokes is always very bad idea. Especially with module in version 0.014. Sorry, your advice is very bad.
BTW I don't know what you mean by symbolically because neither mine neither your code does anything important symbolically.
You are correct if this access happens in a single well defined place in the code. On the other hand, if key-value lookup is the name of the game, you are going to either have a 1) helper subroutine (negating the speed advantage); or 2) litter the code with no strict 'refs'. I'd rather use a data structure used for key-value lookup.
You are right that key-vale structure is better for this kind of job. The question is why OP ended up in this situation at all. You either want constant with some value littered all around in the code or you want a general solution but then it is usually at one place and you can use hash literal/constant there.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.