views:

194

answers:

3

I have a set of constants declared in Perl:

   use constant C1 => 111;
   use constant C2 => 222;
   ..
   use constant C9 => 999;
   my $which_constant = "C2";

How do I construct a Perl expression which, based on $which_constant, derives the value of a constant named with the value of this variable - e.g. "222".

Please note that I can not change any of the conditions above - they are a simplification of a real scenario: I have a module (which I have no control over) from which these constants are imported. The `name of one of the constants is supplied by the user from command line. I need to access the appropriate constants' value.

I've been beating my head against the wall (mostly around all sorts of weird glob constructs) but none of them work.

P.S. If the solution accesses the constants inside their native module - say My::Constants::C2 (without needing to import them), even better, but not necessary - I can import the correct constants into main:: easily using My::Constants->import($which_constant). and yes, to top it off, te constants are NOT exported by default thus needing the explicit import() call.

Some of the things I tried:

  • main::$which_constant - syntax error

  • main::${which_constant} - syntax error

  • ${*$which_constant} - returns empty value

  • *$which_constant - returns "*main::C2"

  • ${*${*which_constant}} - empty

+6  A: 

Perl "constants" are actually subroutines that return a constant value. The perl compiler is able to replace them with the appropriate value at compile time. However, since you want to get the value based on a runtime name lookup, you should do:

&{$which_constant}();

(And of course you need no strict 'refs' somewhere.)

JSBangs
mobrule
ephemient
JS - +1 for reminding me about "constants are just subroutines" which skipped my mind (your answer was before Sinan's so that is well-deserved even if his solution was more to my liking and thus "accepted".
DVK
+12  A: 

Constants defined by constant.pm are just subroutines. You can use method invocation syntax if you have the name of the constant in a string:

#!/usr/bin/perl -l

use strict; use warnings;
use constant C1 => 111;
use constant C2 => 222;

print __PACKAGE__->$_ for qw( C1 C2 );
# or print main->$_ for qw( C1 C2 );

This way, if you try to use a constant which is not defined, you will get an error.

Sinan Ünür
You 'da man Sinan!
DVK
This is a likely candidate for accepted answer, unless someone come up with something even craftier (unlikely but this is Perl we are talking about :) )
DVK
@DVK thank you ;-)
Sinan Ünür
Yeah, using `->` to imply subroutine evaluation is pretty slick. Definitely prettier than my method.
JSBangs
In this scenario, you can say `My::Constants->$which_constant` and skip the `import` step entirely. For better error checking, you could also check `My::Constants->can($which_constant)` if you wanted to make sure the constant existed (although that wouldn't distinguish between constants and other subs.
cjm
+3  A: 

Sinan's suggestion to use method invocation semantics to get around strict 'refs' limits is the cleanest, easiest to read solution.

My only concern about this was that the speed penalty for using this approach might be a problem. We've all heard about method call performance penalties and the speed benefits of inlineable functions.

So I decided to run a benchmark (code and results follow).

The results show that normal, inlined constants run about twice as fast as method calls with a literal subroutine name, and almost three times as fast as method calls with variable subroutine names. The slowest approach is a standard deref and invocation of no strict "refs";.

But, even the slowest approach is pretty darn fast at over 1.4 million times a second on my system.

These benchmarks obliterate my one reservation about using the method call approach to solve this problem.

use strict;
use warnings;

use Benchmark qw(cmpthese);

my $class = 'MyConstant';
my $name  = 'VALUE';
my $full_name = $class.'::'.$name;


cmpthese( 10_000_000, {
    'Normal'      => \&normal_constant,
    'Deref'       => \&direct_deref,
    'Deref_Amp'   => \&direct_deref_with_amp,
    'Lit_P_Lit_N' => \&method_lit_pkg_lit_name,
    'Lit_P_Var_N' => \&method_lit_pkg_var_name,
    'Var_P_Lit_N' => \&method_var_pkg_lit_name,
    'Var_P_Var_N' => \&method_var_pkg_var_name,
});

sub method_lit_pkg_lit_name {
    return 7 + MyConstant->VALUE;
}

sub method_lit_pkg_var_name {
    return 7 + MyConstant->$name;
}

sub method_var_pkg_lit_name {
    return 7 + $class->VALUE;
}

sub method_var_pkg_var_name {
    return 7 + $class->$name;
}

sub direct_deref {
    no strict 'refs';
    return 7 + $full_name->();
}

sub direct_deref_with_amp {
    no strict 'refs';
    return 7 + &$full_name;
}

sub normal_constant {
    return 7 + MyConstant::VALUE();
}

BEGIN {
    package MyConstant;

    use constant VALUE => 32;
}

And the results:

                 Rate Deref_Amp Deref Var_P_Var_N Lit_P_Var_N Lit_P_Lit_N Var_P_Lit_N Normal
Deref_Amp   1431639/s        --   -0%         -9%        -10%        -29%        -35%   -67%
Deref       1438435/s        0%    --         -9%        -10%        -28%        -35%   -67%
Var_P_Var_N 1572574/s       10%    9%          --         -1%        -22%        -29%   -64%
Lit_P_Var_N 1592103/s       11%   11%          1%          --        -21%        -28%   -63%
Lit_P_Lit_N 2006421/s       40%   39%         28%         26%          --         -9%   -54%
Var_P_Lit_N 2214349/s       55%   54%         41%         39%         10%          --   -49%
Normal      4353505/s      204%  203%        177%        173%        117%         97%     --

Results generated with ActivePerl 826 on Windows XP, YMMV.

daotoad
For my current task it was irrelevant (one time config call) but it's a valid concern, in general. I have had MAJOR slowdowns in Perl projects which were rectified by redesigns removing method calls in tight loops after the benchmarking comparing the two.
DVK