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