Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot

Crazy constant question...

by pileofrogs (Priest)
on Dec 15, 2010 at 20:37 UTC ( #877369=perlquestion: print w/replies, xml ) Need Help??
pileofrogs has asked for the wisdom of the Perl Monks concerning the following question:

Greetings Monks!

This question is probably more trouble than it's worth, but it's been bugging me, so here goes:

I can create constants like so:

use constant { FOO => 1, BAR => 2 };
but if I want a way to convert the number back into a string, I need to do something that defines the same relationships again, like
my @num_to_name = qw(FOO BAR);
which allows me to do things like:
my $thing = FOO; print "Thing = ".$num_to_name[$thing]."\n";

My problem is, I hate that I have to maintain my constant declarations and my @num_to_name list separately. If I was doing this with a large number of constants, I'd screw it up for sure.

I actually made a thing that sort of does what I want, though It's cludge-tastic and probably all wrong and unnecessarily stupid:

my @num_to_name; BEGIN { @num_to_name = qw(FOO BAR); for my $number ( 0 .. $#things) { *blah = eval "*My::Package::".$things[$number]; *blah = sub { $number }; } }

With that monster, $num_to_name[FOO] eq 'FOO' no matter what. It loses the inlining of use constant, which is part of what's lame about it.

Is there a way to do this that sucks less?

Thank you for your indulgence


Replies are listed 'Best First'.
Re: Crazy constant question...
by SuicideJunkie (Vicar) on Dec 15, 2010 at 20:57 UTC

    XY check: Are these trying to emulate enumerated types? If not, might hashes make more sense?

    Could you not do something like:

    my %consts = (FOO => 42, BAR => 99); my %names = reverse %consts;
    then make the hashes read only or base your use constants on them?

Re: Crazy constant question...
by kennethk (Abbot) on Dec 15, 2010 at 21:01 UTC
    Sticking an evaled use constant in a BEGIN block would seem to meet your requirement, and avoid those nasty typeglobs:
    #!/usr/bin/perl use strict; use warnings; my @num_to_name; BEGIN { @num_to_name = qw(FOO BAR); eval "use constant $num_to_name[$_] => $_" for 0 .. $#num_to_name; } print "FOO-->", FOO(), "-->", $num_to_name[FOO];

    I've assumed your names are necessarily 1:1 and adjacent integers starting at 0 - this is what your code seems to do. You could do something with hashes instead for more flexibility.

      Avoiding eval:

      my @num_to_name; BEGIN { @num_to_name = qw(FOO BAR); require constant; constant->import( $num_to_name[$_] => $_ ) for 0 .. $#num_to_name; }

      Avoiding eval and multiple calls to import:

      my @num_to_name; BEGIN { @num_to_name = qw(FOO BAR); require constant; constant->import({ map { $num_to_name[$_] => $_ } 0..$#num_to_name + }); }
Re: Crazy constant question...
by johna (Monk) on Dec 15, 2010 at 21:22 UTC
    This might work as well by inspecting the %constant::declared hash:
    #!/usr/bin/perl use strict; use warnings; use constant { FOO => 1, BAR => 2, BAZ => 1 }; my %val_to_name; { no strict 'refs'; foreach my $con (keys(%constant::declared)) { push @{$val_to_name{$con->()}}, $con; } } my $thing = FOO; print "$thing defined by: " . join(" and ", @{$val_to_name{$thing}})." +\n";
    Used a HoA to allow for multiple constants having the same value.
Re: Crazy constant question...
by moritz (Cardinal) on Dec 15, 2010 at 21:23 UTC
    For your information, the inlining has two conditions: the sub needs a prototype of (), and the content needs to be a constant. So it would have to be something like:
    *thing = eval "sub () { '$value' }"

    Though calling import from constant is certainly the better solution.

Re: Crazy constant question...
by SilasTheMonk (Chaplain) on Dec 15, 2010 at 22:16 UTC
    Have you considered Readonly. It allows the read only variables to be interpolated into strings and there is an XS implementation so ought to be fast. I found the syntax a little awkward at first. You have to use '=>' and not '=', which is obvious when you think what is going on, but not so obvious from first principles.
Re: Crazy constant question...
by thargas (Deacon) on Dec 16, 2010 at 14:04 UTC
    How about dualvar from Scalar::Util? This will allow you to assign both a number and a string to a scalar and you'll get the number back in numeric context and the string in string context. Sounds like it fulfills the requirements.
Re: Crazy constant question...
by aartist (Scribe) on Dec 16, 2010 at 16:40 UTC
    From the book "Perl Best Practices". This is the one of the reason why Readonly is better than 'constant'.
    use Readonly; Readonly my $FOO => 1; Readonly my $BAR => 1; my $thing = $FOO; $Things[$thing] = 'whatever'; print $Things[$thing];
Re: Crazy constant question...
by AnomalousMonk (Canon) on Dec 16, 2010 at 14:58 UTC

    Further to the suggestions of SilasTheMonk and thargas, an example. Note that numeric context must now be supplied explicitly. Also, I have no idea what this does to execution speed. Caveat Programmor.

    c:\@Work\Perl>perl -wMstrict -le "use Scalar::Util qw(dualvar); use Readonly; Readonly my $foo => dualvar 42, 'FOO'; print qq{foo ($foo) = }, 0+$foo; $foo = '11'; print qq{after ro mod}; " foo (FOO) = 42 Modification of a read-only value attempted at -e line 1

    (Read-only modification also fails with numeric assignment.)

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://877369]
Approved by ww
Front-paged by ww
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2017-04-26 09:37 GMT
Find Nodes?
    Voting Booth?
    I'm a fool:

    Results (471 votes). Check out past polls.