Vince
Vince

Reputation: 1527

Define constants in Perl with variable name

I would like to create perl-constants from a configuration file. I'm using Config::File to read a config file that looks like this:

ABC = DEF
GHI = JKL

With Config::File that creates a hashref which looks like this:

$VAR1 = {
    'ABC' => 'DEF',
    'GHI' => 'JKL'
};

I would like to use that hashref to create constants where the name of the constant should be the key and the value should be the corresponding value from the hashref. Manually I would do something like

use constant ABC => 'DEF';
use constant GHI => 'JKL';

I tried doing something this:

foreach my $const (keys %$hashref) {
    use constant $const => $keys->{$const};
}

but as expected that doesn't work. Is there a way to achieve what I'm trying to do?

Upvotes: 1

Views: 1740

Answers (2)

ikegami
ikegami

Reputation: 385496

First of all, you can do

use constant {
   CONST1 => VAL1,
   CONST2 => VAL2,
   ...
};

use constant LIST

is equivalent to

BEGIN {
   require constant;
   constant->import(LIST);
}

or

use constant qw( );
BEGIN {
   constant->import(LIST);
}

so you can do

use constant qw( );
use FindBin  qw( $RealBin );
use JSON::XS qw( decode_json );

BEGIN {
   my $qfn = "$RealBin/constants.json";
   open(my $fh, '<:raw', $qfn) or die $!;
   my $file; { local $/; $file = <$fh>; }
   my $constants = decode_json($file);  # { 'ABC' => 'DEF', 'GHI' => 'JKL' };
   constant->import($constants);
}

A cleaner solution might be to use a mini module.

package MyConstantsFromFile;

use strict;
use warnings;

use constant qw( );
use JSON::XS qw( decode_json );

sub import {
   my $class = shift;
   my $qfn = shift;
   open(my $fh, '<:raw', $qfn) or die $!;
   my $file; { local $/; $file = <$fh>; }
   my $constants = decode_json($file);  # { 'ABC' => 'DEF', 'GHI' => 'JKL' };
   my $import = constant->can('import');
   @_ = ('constant', $constants);
   goto &$import;
}

1;

use FindBin qw( $RealBin );
use MyConstantsFromFile "$RealBin/constants.json";

Upvotes: 5

Sobrique
Sobrique

Reputation: 53478

I'm posting this as an answer, to pick up from the comments - hashes vs. constants:

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
use Benchmark qw(:all);

use constant ABC => 'DEF';
use constant GHI => 'JKL';

my $ABC = 'DEF';
my $GHI = 'JKL';

my %test_hash = (
   ABC => 'DEF',
   GHI => 'JKL'
);

sub access_hash {
   my $value  = $test_hash{ABC};
   my $value2 = $test_hash{DEF};
}

sub access_constant {
   my $value  = ABC;
   my $value2 = GHI;
}

sub access_scalar { 
    my $value = $ABC;
    my $value2 = $GHI;
} 

my $count = 100_000_000;

cmpthese(
   $count,
   {  'hash'     => \&access_hash,
      'constant' => \&access_constant,
      'scalar'   => \&access_scalar
   }
);

Results:

               Rate     hash   scalar constant
hash      9427736/s       --      -7%     -10%
scalar   10143017/s       8%       --      -3%
constant 10492078/s      11%       3%       --

So you're right - it's faster to use a constant. However, I'd suggest for an operation that runs at a rate of 10M/sec, 'saving' 5% (or even 10%) is simply not worth the hackery you'll need to do it.

But in the interests of actually answering the question - the root of this problem is that constant is defined at compile time, where variables ... aren't.

There's simply no hash existing when the constant is defined, so that isn't going to work. You're also quite limited in what you can actually do in a BEGIN block. My thought would be that you could probably run a 'compile' process to turn your config file into a .pm that you could then use.

package import_const;

use constant ABC => 'DEF';
use constant GHI => 'JKL';

Then use import_const; and access your constants as import_const::ABC. (Or use Exporter to bring them into the local namespace).

sub from_pkg { 
    my $value  = import_const::ABC;
    my $value2 = import_const::GHI;
}

Add that into the timing test:

                        Rate        hash     scalar imported constant   constant
hash               9497578/s          --        -6%               -9%        -9%
scalar            10063399/s          6%         --               -4%        -4%
imported constant 10473398/s         10%         4%                --        -0%
constant          10492078/s         10%         4%                0%         --

I think I'd still argue that the gains are marginal for the effort. Especially given use constant will surprise you with it's evil

There may be a module that can do what you need though:

CPAN modules for defining constants

Upvotes: 6

Related Questions