http://qs321.pair.com?node_id=877369

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

--Pileofrogs

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 AnomalousMonk (Archbishop) 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.)

Re: Crazy constant question...
by aartist (Pilgrim) 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];