How can I reduce duplication in constants? - perl

I have this Perl script with many defined constants of configuration files. For example:
use constant {
LOG_DIR => "/var/log/",
LOG_FILENAME => "/var/log/file1.log",
LOG4PERL_CONF_FILE => "/etc/app1/log4perl.conf",
CONF_FILE1 => "/etc/app1/config1.xml",
CONF_FILE2 => "/etc/app1/config2.xml",
CONF_FILE3 => "/etc/app1/config3.xml",
CONF_FILE4 => "/etc/app1/config4.xml",
CONF_FILE5 => "/etc/app1/config5.xml",
};
I want to reduce duplication of "/etc/app1" and "/var/log" , but using variables does not work. Also using previously defined constants does not work in the same "use constant block". For example:
use constant {
LOG_DIR => "/var/log/",
FILE_FILENAME => LOG_DIR . "file1.log"
};
does not work.
Using separate "use constant" blocks does workaround this problem, but that adds a lot of unneeded code.
What is the correct way to do this?
Thank you.

Using separate "use constant" blocks
does workaround this problem, but that
adds a lot of unneeded code.
Does it really?
use constant BASE_PATH => "/etc/app1";
use constant {
LOG4PERL_CONF_FILE => BASE_PATH . "/log4perl.conf",
CONF_FILE1 => BASE_PATH . "/config1.xml",
CONF_FILE2 => BASE_PATH . "/config2.xml",
CONF_FILE3 => BASE_PATH . "/config3.xml",
CONF_FILE4 => BASE_PATH . "/config4.xml",
CONF_FILE5 => BASE_PATH . "/config5.xml",
};
I don't see a lot of problems with this. You have specified the base path in one point only, thereby respecting the DRY principle. If you assign BASE_PATH with an environment variable:
use constant BASE_PATH => $ENV{MY_BASE_PATH} || "/etc/app1";
... you then have a cheap way of reconfiguring your constant without having to edit your code. What's there to not like about this?
If you really want to cut down the repetitive "BASE_PATH . " concatenation, you could add a bit of machinery to install the constants yourself and factor that away:
use strict;
use warnings;
use constant BASE_PATH => $ENV{MY_PATH} || '/etc/apps';
BEGIN {
my %conf = (
FILE1 => "/config1.xml",
FILE2 => "/config2.xml",
);
for my $constant (keys %conf) {
no strict 'refs';
*{__PACKAGE__ . "::CONF_$constant"}
= sub () {BASE_PATH . "$conf{$constant}"};
}
}
print "Config is ", CONF_FILE1, ".\n";
But at this point I think the balance has swung away from Correct to Nasty :) For a start, you can no longer grep for CONF_FILE1 and see where it is defined.

I'd probably write it like this:
use Readonly;
Readonly my $LOG_DIR => "/var/log";
Readonly my $LOG_FILENAME => "$LOG_DIR/file1.log";
Readonly my $ETC => '/etc/app1';
Readonly my $LOG4PERL_CONF_FILE => "$ETC/log4perl.con";
# hash because we don't have an index '0'
Readonly my %CONF_FILES => map { $_ => "$ETC/config$_.xml" } 1 .. 5;
However, that's still a lot of code, but it does remove the duplication and that's a win.
Why are your logfiles numeric? If they start with 0, an array is a better choice than a hash. If they're named, they're more descriptive.

use constant +{
map { sprintf $_, '/var/log' } (
LOG_DIR => "%s/",
LOG_FILENAME => "%s/file1.log",
),
map { sprintf $_, '/etc/app1' } (
LOG4PERL_CONF_FILE => "%s/log4perl.conf",
CONF_FILE1 => "%s/config1.xml",
CONF_FILE2 => "%s/config2.xml",
CONF_FILE3 => "%s/config3.xml",
CONF_FILE4 => "%s/config4.xml",
CONF_FILE5 => "%s/config5.xml",
),
};

That's not going to work, sadly. The reason for this is that you are using functions ('constants') before they are defined. You evaluate them before the call to constant->import.
Using variables doesn't work because use statements are evaluated at compile time. Assigning to variables is only done at runtime, so they won't be defined yet.
The only solution I can give is to split it into multiple use constant statements. In this case, two statements will do (one for LOG_DIR and CONF_DIR, another for the rest).

Depending on what you are doing, you might not want constants at all. Mostly, I write stuff that other people use to get their stuff done, so I solve this problem in a way that gives other programmers flexibility. I make these things into methods:
sub base_log_dir { '...' }
sub get_log_file
{
my( $self, $number ) = #_;
my $log_file = catfile(
$self->base_log_dir,
sprintf "foo%03d", $number
);
}
By doing it this way, I can easily extend or override things.
Doing this loses the value of constant folding though, so you have to think about how important that is to you.

Related

Perl hash referencing itself at declaration

When declaring a Perl hash, I'm wondering if it's possible to use a value that was assigned earlier in the declaration.
I'd like to do the equivalent of this, all in one shot:
my %H = (something => generateString());
$H{foo} = $H{something} . "/FOO",
$H{bar} = $H{something} . "/BAR",
I can imagine something like this:
my %H = (
something => generateString(),
foo => $_{something} . "/FOO",
bar => $_{something} . "/BAR",
);
EDIT: To be clear, I don't care about an actual reference to $H{something} (i.e. changing $H{something} later shouldn't affect $H{foo}). I'd just like to get its value into the string concatenations.
You seem to think there are two assignment operators in
%H = ( a=>1, b=>$H{a} );
There isn't. Keep in mind the above is identical to
%H = ( 'a', 1, 'b', $H{a} );
There's one assignment operator, and before you can perform the assignment, you need to know what is going to be assignment.
What I'm saying is that the real problem with %H = ( a=>1, b=>$H{a} ); isn't one of scope; the real problem is that nothing been assigned to %H when you do $H{a}[1]. As such, $_{a} makes more sense than $H{a}.
The solution is simple:
my $something = generateString();
my %H = (
something => $something,
foo => "$something/FOO",
bar => "$something/BAR",
);
%H hasn't even been created yet!
my %H = (something => generateString());
%H = (%H,
foo => $H{something} ."/FOO",
bar => $H{something} ."/BAR",
);
seems reasonable, but if you want it in one shot at ANY cost,
use strict;
use warnings;
%{ $_->{hash} } = (
something => $_->{thing},
foo => "$_->{thing}/FOO",
bar => "$_->{thing}/BAR",
)
for {hash => \my %H, thing => generateString()};
which can be translated to more verbose version,
my %H;
local $_ = {hash => \%H, thing => generateString()};
%{ $_->{hash} } = (
something => $_->{thing},
foo => "$_->{thing}/FOO",
bar => "$_->{thing}/BAR",
);
As ikegami says, there is only one assignment operation; it is executed after the entire right hand side is evaluated.
A couple alternatives:
my %H = map {;
'something' => $_,
'foo' => "$_/FOO",
'bar' => "$_/BAR",
} generateString();
and
use Memoize;
memoize('generateString');
my %H = (
'something' => scalar(generateString()),
'foo' => generateString() . '/FOO',
'bar' => generateString() . '/BAR',
);
(scalar needed because otherwise, memoize will make separate calls for list and scalar context.)
An alternative is to do the definition as part of the assignments:
my %H = (
something => ( local our $str=generateString() ),
foo => qq{$str/FOO},
bar => qq{$str/BAR}
);
There are two things to consider:
Notice the use of local our instead of my
I advise against this. For many reasons; if not for best coding practices, but for bugs that may be introduced.
I feel that defining a variable outside the hash definition (as shown in Ikegami's answer) is the best way to go for many reasons, but I realize that's also not the question being asked. The answer to the question is that a hash cannot be referenced before it is created and during creation it is not exposed within the constructor.

Moose's attribute vs simple sub?

How to decide - what is the recommended way for the next code fragment?
I have a Moose-based module, where some data is a simple HashRef.
It is possible to write - as a Mooseish HashRef, like:
package Some;
has 'data' => (
isa => 'HashRef',
builder => '_build_href',
init_arg => undef,
lazy => 1,
);
sub _build-href {
my $href;
$href = { a=>'a', b=>'b'}; #some code what builds a href
return $href;
}
vs
sub data {
my $href;
$href = { a=>'a', b=>'b'}; #some code what builds a href
return $href;
}
What is the difference? I'm asking because when calling:
my $obj = Some->new;
my $href = $obj->data;
In both case I get a correct HashRef. So when is it recommended to use a Moose-ish has construction (which is longer) vs a simple data sub?
PS: probably this question is so simple for an average perl programmer, but please, keep in mind, I'm still only learning perl.
If you have an attribute, then whoever is constructing the object can set the hashref in the constructor:
my $obj = Some->new(data => { a => 'c', b => 'd' });
(Though in your example, you've used init_arg => undef which would disable that ability.)
Also, in the case of the attribute, the builder is only run once per object while with a standard method, the method might be called multiple times. If building the hashref is "expensive", that may be an important concern.
Another difference you'll notice is with this:
use Data::Dumper;
my $obj = Some->new;
$obj->data->{c} = 123;
print Dumper( $obj->data );

Perl - Overloading package/class properties?

I am attempting to port some code from PHP that basically boils down to property overloading. That is, if you try to get or set a class property that is not actually defined as a part of the class, it will send that information to a function that will pretty much do anything I want with it. (In this case, I want to search an associative array within the class before giving up.)
However, Perl is... quite a bit different from PHP, given that classes are already hashes. Is there any way that I can apply some equivalent of __get() and __set() to a Perl "class" that will remain completely encapsulated in that package, transparent to anything trying to actually get or set properties?
EDIT: The best way to explain this may be to show you code, show the output, and then show what I want it to output.
package AccessTest;
my $test = new Sammich; #"improper" style, don't care, not part of the question.
say 'bacon is: ' . $test->{'bacon'};
say 'cheese is: ' . $test->{'cheese'};
for (keys $test->{'moreProperties'}) {
say "$_ => " . $test->{'moreProperties'}{$_};
}
say 'invalid is: ' . $test->{'invalid'};
say 'Setting invalid.';
$test->{'invalid'} = 'true';
say 'invalid is now: ' . $test->{'invalid'};
for (keys $test->{'moreProperties'}) {
say "$_ => " . $test->{'moreProperties'}{$_};
}
package Sammich;
sub new
{
my $className = shift;
my $this = {
'bacon' => 'yes',
'moreProperties' => {
'cheese' => 'maybe',
'ham' => 'no'
}
};
return bless($this, $className);
}
This currently outputs:
bacon is: yes
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 11.
cheese is:
cheese => maybe
ham => no
Use of uninitialized value in concatenation (.) or string at ./AccessTest.pl line 17.
invalid is:
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
Now, I need to make modifications to Sammich only, without making any changes at all to the initial AccessTest package, that will result in this:
bacon is: yes
cheese is: maybe
cheese => maybe
ham => no
invalid is: 0
Setting invalid.
invalid is now: true
cheese => maybe
ham => no
invalid => true
As you can see, the desired effect is that the 'cheese' property, since it's not a part of the test object directly, would instead be grabbed from the 'moreProperties' hash. 'invalid' would attempt the same thing, but since it is neither a direct property nor in 'moreProperties', it would act in whatever way programmed - in this case, I would want it to simply return the value 0, without any errors or warnings. Upon attempting to set the 'invalid' property, it would not be added to the object directly, because it's not already there, but would instead be added to the 'moreProperties' hash.
I'm expecting this to take more than the six or so lines it would require in PHP, but as it is a very important concept of OOP, I fully expect Perl to handle it somehow.
As I have said in my comments, the reason this problem is hard is that you aren't following one of the golden rules of Object-Oriented Programming, namely encapsulation. How can you expect to intercept a call that isn't a method? If your exposed API consists of getter/setters then you can intercept an unknown method call with an AUTOLOAD method. If you don't you may use #pilcrow's noble suggestion of using a tied hash (Edit: or #tchrist's valiant use of the overload pragma); still this is more a tribute to Perl's flexibility than your API.
To do this more correctly (and yes I see you "require" that the API not be modified, if you choose to ignore this, then call this post a message to future readers).
#!/usr/bin/env perl
use v5.10; # say
use strict;
use warnings;
use MooseX::Declare;
use Method::Signatures::Modifiers;
class Sammich {
has 'bacon' => ( isa => 'Str', is => 'rw', default => 'yes' );
has 'more' => (
isa => 'HashRef',
is => 'rw',
default => sub{ {
cheese => 'maybe',
ham => 'no',
} },
);
our $AUTOLOAD;
method AUTOLOAD ($val?) {
# get unknown method name
my $attr = (split( /::/, $AUTOLOAD))[-1];
my $more = $self->more;
# see if that method name exists in "more"
if (exists $more->{$attr}) {
# if so, are there new values? then set
if (defined $val) {
$more->{$attr} = $val;
}
# ... and return
return $more->{$attr};
}
# attr does not exist, so set it or initialize it to 0
$more->{$attr} = defined $val ? $val : 0;
return $more->{$attr};
}
}
# I don't care that you don't care
my $test = Sammich->new();
say 'bacon is: ' . $test->bacon;
say 'cheese is: ' . $test->cheese;
for (keys %{ $test->more }) {
say "$_ => " . $test->more->{$_};
}
say 'invalid is: ' . $test->invalid;
say 'Setting invalid.';
$test->invalid( 'true' );
say 'invalid is now: ' . $test->invalid;
for (keys %{ $test->more }) {
say "$_ => " . $test->more->{$_};
}
Some may say that my wording here is harsh and perhaps it is. I try to help those who will be helped, therefore seeing a bolded message like
I need to make modifications to Sammich only, without making any changes at all to the initial AccessTest package
then demanding that Perl bow to your whim
I'm expecting this to take more than the six or so lines it would require in PHP, but as it is a very important concept of OOP, I fully expect Perl to handle it somehow.
is irksome. I hope that future readers will see this as a case example of why encapsulation helps in the long run.
Update to the update
(I am receiving anonymous downvotes, presumably for abetting your misguided approach. :) )
Just to be clear, the question you pose is an "XY Problem", specifically an artifact of the mistaken translation of OO technique from PHP to Perl. For example, as mentioned passim in this question, object properties in perl generally should not be implemented as directly accessed hash(ref) elements. That's the wrong "X".
Jumping from one language to another introduces more than merely syntactical differences.
Update
You can accomplish what you want with something like this:
package UnfortunateHack; {
use Tie::Hash;
our #ISA = qw(Tie::StdHash);
sub FETCH {
my ($self, $key) = #_;
return exists $self->{$key}
? $self->{$key}
: $self->{moreProperties}->{$key};
}
}
...
package Sammich;
sub new {
my $class = shift;
tie my %this, 'UnfortunateHack';
%this = ( bacon => 'yes',
moreProperties => { ... } );
bless \%this, $class;
}
Original Answer
If I understand your intent — to intercept $obj->property calls where TheClass::property isn't necessarily defined — then perl's AUTOLOAD in an OO context will do what you want.
From the linked docs (perlobj):
If you call a method that doesn't exist in a class, Perl will throw an error. However, if that class or any of its parent classes defines an AUTOLOAD method, that AUTOLOAD method is called instead.
You are completely violating encapsulation. To prove it to you, comment out the bless from &Sammich::new so that it returns a plain hash reference.
package Sammich;
sub new {
my $className = shift;
my $this = {
'bacon' => 'yes',
'moreProperties' => {
'cheese' => 'maybe',
'ham' => 'no'
}
};
# don't even bother blessing it
# return bless($this, $className);
}
The only way to get what you want it is to use magic.
In Perl classes are more than hashes, they are built on packages and you can define there whatever method you want and it remains encapsulated in that package/class.
You can see a code example in the Object-Oriented Programming in Perl Tutorial.

Hash Constants in Perl

I have a situation where I have an application and it maps to a directory I need to process in a zipfile. The mapping is quite simple:
CWA => "Financial",
PIP => "",
IFA => "IFA",
VDX => "Financial,
That is, if the name of the file begins with CWA, I know the directory I have to munge is under Financial. If the file name begins with a IFA, I know the directory name is IFA. I'd like to set this up as a hash (easy enough), but since these values don't really change, I'd like to setup this key => value mapping as a hash constant.
I don't believe this is possible, so I'd like to do the next best thing. What would that be? Or, can you setup a hash constant?
I'm thinking of writing a subroutine where you pass a parameter and it returns the correct value. After all, it's really the way constants themselves work, and it'd guarantee that the relationships between the keys and values don't change through out the program.
Or, I can simply declare the key => value relationship in the beginning of my program and hope that the key => value pairs aren't modified by something. This would be easier to read, and easier to modify if you have to since it's on the very top of my source code.
What's the best way to implement a key => value constant?
You can use Const::Fast.
use Const::Fast;
const my %hash = (
CWA => "Financial",
PIP => "",
IFA => "IFA",
VDX => "Financial",
);
Just use a named hash. Most likely, nothing will go wrong.
Use Readonly. This makes a hash that is accessed like any other hash, but can't be modified unless someone starts mucking around in the perl internals. As stated in its docs, it's slow compared to a regular variable access, but it's very unlikely to be slow enough to matter for you.
Hide the hash in a subroutine.
.
sub get_directory_for_prefix {
my ($prefix) = #_;
my %map = (
CWA => "Financial",
PIP => "",
IFA => "IFA",
VDX => "Financial",
);
return $map{$prefix};
}
or even
sub get_directory_for_prefix {
{CWA=>"Financial",PIP=>"",IFA=>"IFA",VOX=>"Financial"}->{shift()};
};
Alternativelly, if you don't want to use blocks you could still use constant:
use strict;
use warnings;
use constant CONSHASH => sub {{
foo1 => 'bar1',
foo2 => 'bar2',
foo3 => 'bar3',
}->{ +shift }};
print CONSHASH->('foo1') . "\n";
print CONSHASH->('foo2') . "\n";
print CONSHASH->('foo3') . "\n";
Here what I finally did following a few suggestions:
{
my %appHash = (
CWA => "Financial",
PIP => "",
FIA => "FIA",
VDX => "Financial",
);
sub appDir {
return $appHash{+shift};
}
}
By putting the %appHash in its own block, I can't reference that hash in the rest of my code. However, the appDir subroutine is in the same block and can reference it. And, because Subroutines are package wide, I can access that subroutine in my code. Therefore, I can access the values of %appHash, but I cannot change them.
use strict;
use warnings;
{
my %appHash = (
CWA => "Financial",
PIP => "",
FIA => "FIA",
VDX => "Financial",
);
sub appDir {
return $appHash{+shift};
}
}
{
### WARNING.
### this code is a counter example to show that %appHash
### is not available in this scope.
### Do not use this code.
### Disabling strictures and warnings in this block only.
no strict;
no warnings; # Danger Will Robinson!
# disable explicit package name error because %appHash is not defined in this scope.
# disable warnings related to 1) main::appHash and 2) uninitialized value of $appHash{"CWA"}
print "The directory for CWA is " . $appHash{CWA} . "\n"; #Prints nothing
}
print "The directory for CWA is " . appDir("CWA") . "\n"; #Prints Financial
my %appHash;
$appHash{CWA} = "FOO FOO FOO!";
print "The directory for CWA is " . $appHash{CWA} . "\n"; #Prints FOO FOO FOO!
print "The directory for CWA is " . appDir("CWA") . "\n"; #Still prints Financial
Neat!
Thanks iwj and hobbs

How can I make all lazy Moose features be built?

I have a bunch of lazy features in a Moose object.
Some of the builders require some time to finish.
I would like to nvoke all the builders (the dump the "bomplete" object).
Can I make all the lazy features be built at once, or must I call each feature manually to cause it builder to run?
If you want to have "lazy" attributes with builders, but ensure that their values are constructed before new returns, the usual thing to do is to call the accessors in BUILD.
sub BUILD {
my ($self) = #_;
$self->foo;
$self->bar;
}
is enough to get the job done, but it's probably best to add a comment as well explaining this apparently useless code to someone who doesn't know the idiom.
Maybe you could use the meta class to get list of 'lazy' attributes. For example:
package Test;
use Moose;
has ['attr1', 'attr2'] => ( is => 'rw', lazy_build => 1);
has ['attr3', 'attr4'] => ( is => 'rw',);
sub BUILD {
my $self = shift;
my $meta = $self->meta;
foreach my $attribute_name ( sort $meta->get_attribute_list ) {
my $attribute = $meta->get_attribute($attribute_name);
if ( $attribute->has_builder ) {
my $code = $self->can($attribute_name);
$self->$code;
}
}
}
sub _build_attr1 { 1 }
sub _build_attr2 { 1 }
I've had this exact requirement several times in the past, and today I actually had to do it from the metaclass, which meant no BUILD tweaking allowed. Anyway I felt it would be good to share since it basically does exactly what ether mentioned:
'It would allow marking attributes "this is lazy, because it depends
on other attribute values to be built, but I want it to be poked
before construction finishes."'
However, derp derp I have no idea how to make a CPAN module so here's some codes:
https://gist.github.com/TiMBuS/5787018
Put the above into Late.pm and then you can use it like so:
package Thing;
use Moose;
use Late;
has 'foo' => (
is => 'ro',
default => sub {print "setting foo to 10\n"; 10},
);
has 'bar' => (
is => 'ro',
default => sub {print 'late bar being set to ', $_[0]->foo*2, "\n"; $_[0]->foo*2},
late => 1,
);
#If you want..
__PACKAGE__->meta->make_immutable;
1;
package main;
Thing->new();
#`bar` will be initialized to 20 right now, and always after `foo`.
#You can even set `foo` to 'lazy' or 'late' and it will still work.