1

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?

9
  • 5
    use constants is a pragma that gets called at compile time. Your variables are only there at run time. You cannot do that outside of a BEGIN block or in an eval. I'll write an answer in a bit with alternatives and explanation. Commented Feb 6, 2017 at 9:28
  • 2
    I have to say, I've never really seen much value in 'use constant' in perl. It seems to be a bit more brittle than ... well, just 'not'. Commented Feb 6, 2017 at 9:42
  • 3
    I'd just use a hash, and not constantify them. Commented Feb 6, 2017 at 9:44
  • 2
    I echo @Sobrique's suggestion of using a hash. Using constants over a hash sounds like a micro-optimization Commented Feb 6, 2017 at 10:06
  • 3
    If performance is a concern, then ... I still wouldn't do this. But write your code the 'easy/obvious' way, then profile it to see where the hotspots are. I'd be pretty confident that that amount of time you save isn't particularly significant. Commented Feb 6, 2017 at 10:14

2 Answers 2

6

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

Sign up to request clarification or add additional context in comments.

3 Comments

Using the configuration file as code and embedding it into the configuration module would work, but that's not a good way to do that, since inducing code from an external source is not a good practice. Thanks for the answer, though, I'll have to look at other ways to implement what I need!
... yes, but introducing code from another source is what you're trying to do, by 'compiling' constants off a dynamic input.
Anyway, you may find Review of 'constant' options' to be useful.
5

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

Comments

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.