nameofmyuser has asked for the wisdom of the Perl Monks concerning the following question:

Hello Monks, I'm having trouble declaring a constant, which is a list of one hash value, to another hash value (using the .. range operator). Here is a SSCCE:
use strict; use warnings; use Data::Dumper; my $hashref = () = { 'ONE' => 1, 'TWO' => 2, 'THREE' => 3, 'FOUR' => 4, }; use constant HASHREFCONST => [$hashref->{ONE} .. $hashref->{FOUR}]; print Dumper(HASHREFCONST);
This results in:
Use of uninitialized value in range (or flop) Use of uninitialized value in range (or flop)
I'm a little puzzled as to what's going on here. Use directives occur at compile time, so I understand that perl is attempting to set the constant at compile time. However, why does this work as expected, then?
my @test = ($hashref->{ONE} .. $hashref->{TWO}); use constant GOODCONSTANT => \@test;
I assume the @test array is allocated at compile time, and it's reference is set to GOODCONSTANT also at compile time. Then it's populated at runtime, but the constant is still pointing to the same memory. If this is the case, then it makes sense that my first method won't ever work. Is there a workaround to get the first method to work? Thanks for taking the time to read this!

Replies are listed 'Best First'.
Re: Using hashref values in constant declarations.
by kcott (Archbishop) on Sep 09, 2017 at 02:37 UTC

    G'day nameofmyuser,

    Welcome to the Monastery.

    Perl provides a number of special blocks which are executed at various points during the lifetime of the program: "BEGIN, UNITCHECK, CHECK, INIT and END". You can use these to check your assumptions about what's happening at these various points.

    [Warning: Do read the linked documentation, especially the begincheck example. Some of those blocks are executed in FIFO order; and some in LIFO order. It's not intuitively obvious which is which.]

    Your assumptions appear to be correct; although, the phrase "@test array is allocated at compile time" could be misinterpreted. To be clear, in both of these:

    my $var1; my $var2 = 'val2';

    The variables are declared at compile time; but no assignment occurs at this time. The "$var2 = 'val2'" part of that second statement occurs at runtime.

    Here's an example using some of those special blocks. It's just a start: modify and extend as necessary.

    #!/usr/bin/env perl -l use strict; use warnings; my $hashref = { a => 1, b => 2 }; my @array = qw{c d e}; use constant { HR_ARRAY => [ sort values %$hashref ], AREF => \@array, }; BEGIN { print join ' ', 'B1:', sort(keys %$hashref), @array } BEGIN { print join ' ', 'B2:', @{+HR_ARRAY}, @{+AREF} } INIT { @array = qw{f g h} } INIT { print join ' ', 'I1:', sort(keys %$hashref), @array } INIT { @array = qw{i j k} } INIT { print join ' ', 'I2:', @{+HR_ARRAY}, @{+AREF} } print join ' ', 'R1:', sort(keys %$hashref), @array; print join ' ', 'R2:', @{+HR_ARRAY}, @{+AREF};

    Output:

    B1: B2: I1: f g h I2: i j k R1: a b c d e R2: c d e

    Using constant for references is generally a bad choice. It makes the token that points to the reference a constant; but, it does not make the values of the reference constants. Consider these:

    $ perl -E 'use constant AREF => [1..4]; say "@{+AREF}";' 1 2 3 4 $ perl -E 'use constant AREF => [1..4]; say "@{+AREF}"; AREF = []' Can't modify constant item in scalar assignment at -e line 1, at EOF Execution of -e aborted due to compilation errors. $ perl -E 'use constant AREF => [1..4]; say "@{+AREF}"; AREF->[2] = 99 +; say "@{+AREF}"' 1 2 3 4 1 2 99 4

    In terms of workarounds: one of the Anonymous Monks showed a BEGIN block; you could potentionally use an INIT block with a variation of what my code showed for AREF; you could also just use two constants, something like this:

    use constant { ONE => 1, ..., FOUR => 4 }; use constant NUM_AREF => [ ONE .. FOUR ];

    [Note: I've used NUM_AREF which could probably be improved upon; however, HASHREFCONST is a poor, and highly misleading, choice for an arrayref constant: it will make subsequent code difficult to understand, and runs a high risk of being the source of coding errors.]

    If you tell us how this code is intended to be used, we can probably offer more concrete suggestions.

    See also: enum — a CPAN module which you might find useful.

    — Ken

      perl -E 'use constant AREF => [1..4]; say "@{+AREF}"; AREF->[2] = 99; say "@{+AREF}"'

      Good post, this bit in particular reminded me of the recent thread constant vector on exactly that topic.

      In terms of workarounds ...

      For the benefit of the novice PerlMonk, it should be emphasized that none of the workarounds address (nor, I'm sure, were intended to address) the total mutability of the content of "constant" references:

      c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -le "use constant { ONE => 1, FOUR => 4 }; use constant AREF => [ ONE .. FOUR ]; dd AREF; ;; AREF->[3] = 99; dd AREF; ;; AREF->[9] = 999; dd AREF; ;; $#{ +AREF } = -1; dd AREF; " [1, 2, 3, 4] [1, 2, 3, 99] [1, 2, 3, 99, undef, undef, undef, undef, undef, 999] []
      A search of MetaCPAN for "lock" yields a couple of interesting-looking modules, neither of which have I used and so cannot recommend: Data::Lock and Array::Lock.

      In general, Perl handles the important concept of immutability very poorly. I have the vague notion that Perl 6 does better in this respect; can anyone comment?


      Give a man a fish:  <%-{-{-{-<

        A search of MetaCPAN for "lock"

        This reminds me of the core module Hash::Util:

        #!/usr/bin/env perl use warnings; use strict; use Hash::Util qw/lock_hash/; my %x = ( foo => "bar" ); lock_hash %x; eval { $x{y}++; 1 } or warn $@; eval { $x{foo}="quz"; 1 } or warn $@; __END__ Attempt to access disallowed key 'y' in a restricted hash at lock.pl l +ine 9. Modification of a read-only value attempted at lock.pl line 10.

        Although there was some discussion on P5P about removing them a while back, I'm not sure if that will happen.

        For completeness, there are also tied hashes, which could implement the same thing.

Re: Using hashref values in constant declarations.(binding)
by LanX (Saint) on Sep 09, 2017 at 09:37 UTC
    >  However, why does this work as expected, then?
    my @test = ($hashref->{ONE} .. $hashref->{TWO}); use constant GOODCONSTANT => \@test;

    Short answer: that's how "binding" works!

    my (like our ) has dual nature:

    • declaration happens at compile time
    • assignment at run time

    Declaration means the following equally named variables in scope are "bound" to a specific "reference" (saying address would be too simplisti/C) when translated to an op tree.

    GOODCONSTANT then points to that reference at compile time, but the content of @test will only be populated at run time.

    Try to access content of GOODCONSTANT at compile time, and you will fail.

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Using hashref values in constant declarations.
by Anonymous Monk on Sep 08, 2017 at 22:08 UTC
    I assume the @test array is allocated at compile time, and it's reference is set to GOODCONSTANT also at compile time. Then it's populated at runtime, but the constant is still pointing to the same memory. If this is the case, then it makes sense that my first method won't ever work. Is there a workaround to get the first method to work?

    Yes, and yes:

    my $hashref; BEGIN { $hashref = { 'ONE' => 1, 'TWO' => 2, 'THREE' => 3, 'FOUR' => 4, }; } use constant HASHREFCONST => [$hashref->{ONE} .. $hashref->{FOUR}];

    Altho that is one heck of a strange way to use hashref to set up a constant... what's wrong with use constant FOO => [1 .. 4];?

Re: Using hashref values in constant declarations.
by LanX (Saint) on Sep 10, 2017 at 11:58 UTC
    For completeness, the following works too:

    use strict; use warnings; use Data::Dumper; use constant HASHREFCONST => do { my $hashref = { 'ONE' => 1, 'TWO' => 2, 'THREE' => 3, 'FOUR' => 4, }; [$hashref->{ONE} .. $hashref->{FOUR}] }; print Dumper(HASHREFCONST);

    $VAR1 = [ 1, 2, 3, 4 ];

    Depends what your real intention is...

    Update

    for instance the use of a range .. looks very dubious!

    Cheers Rolf
    (addicted to the Perl Programming Language and ☆☆☆☆ :)
    Je suis Charlie!

Re: Using hashref values in constant declarations.
by Anonymous Monk on Sep 08, 2017 at 22:29 UTC
    my $hashref = () = {

    Bad use of the "Saturn" operator, have you done print Dumper($hashref);?

Re: Using hashref values in constant declarations.
by swl (Prior) on Sep 11, 2017 at 02:15 UTC

    If what you want is to ensure that the hash contents cannot be changed elsewhere in your program then have a look at ReadonlyX.

Re: Using hashref values in constant declarations.
by Anonymous Monk on Sep 10, 2017 at 13:54 UTC
    "Constant" declarations in Perl are not quite what you might expect them to be based on your experience with other languages. Pay particular attention to the "technical notes" and "caveats" in perldoc constant, and to the inline-functions documentation referenced therein.
Re: Using hashref values in constant declarations.
by Anonymous Monk on Sep 11, 2017 at 00:44 UTC

    If I may say ... your best bet would simply be to say my $HASHREFCONST = { ..., and be done with it.

    In other words, “it is up to you” to see to it that none of the values in this hashref are actually modified ... that all of the programmers who surround you and who follow in your footsteps understand what $UPPER_CASE_ONLY means.

    In the Perl environment, “a hash(ref)” (like an array(ref) ...) is fundamentally a dynamic data-structure, and it always will be.   Thus, while it “makes sense” (IMHO ...) to apply this nomenclature to static values, it does not (and never will ...) make sense for data structures which, in the world-view of the Perl language, are fundamentally dynamic:   lists, arrays, and hashes.