I banged my head on this problem all day, not understanding what the problem was. I finally figured it was time to enlist the monks' help. But when I condensed the problem down to a short test case exposing my dilemma, I naturally found what the problem was. This is therefore no longer a question, but rather a meditation, on the correct use of hash keys defined via use constant.

I was writing a CGI script that displayed a table of IP addresses under DHCP control. I wanted to display addresses that had been allocated, expired and/or in conflict. This was written as a table, with the background cell colour indicating the status. I then had to add a legend, to explain what the colours meant, and at the same time, I thought it would be nice to display the number of values encountered for each legend key.

The hash keys were nothing more than the HTML background colour to be used. Here's the problem, without running the code, what do you think the following will print out:

#! /usr/bin/perl -w use strict; use constant FOO => 'foo bgcolor'; use constant BAR => 'bar bgcolor'; sub keyname { return shift() % 2 ? FOO : BAR } # to simplify my( %g, %h ); $g{ keyname(20) } = 6; $g{ keyname(21) } = 12; $h{ FOO } = 18; $h{ BAR } = 24; print "g: $_ is $g{$_}\n" for keys %g; print "h: $_ is $h{$_}\n" for keys %h;

In the %h hash, I am assigning based on a defined constant. In the %g I am not sure which key to use. All I have is a value, so I call a subroutine that determines the appropriate key. As it turns out, and in hindsight it is perfectly obvious, the two sets of keys assignments are not equivalent. Here is the output:

g: foo bgcolor is 12 g: bar bgcolor is 6 h: BAR is 24 h: FOO is 18

When I was trying to debug the code, however, I couldn't understand why $g{FOO} was returning undef. I was having an impedance mismatch between how I thought constants should behave and how they do behave.

It turns out there are two ways to fix this. The problem is that $h{FOO} is using FOO as a string... literally 'FOO'. It is not calling the constant FOO subroutine. Bleagh! I could have either written the keyname thusly sub keyname { return shift() % 2 ? 'FOO' : 'BAR' } or else I could have written $g{+FOO} when refencing the hash. I tend to discount to first solution, as it appears to circumvent the whole point of using constants. I also thing that the +FOO is a bit of a wart that reveals the retrofittedness of constants in Perl, but I guess I can live with that, at least until Perl 6. So that's what my code now looks like.

<update> yes, as tye points out, there are many ways of writing +FOO. That's the point. You're exposing the gory details of how constants are hacked up in Perl, and I find the whole concept a bit ugly. Especially the way constants fall apart across subroutine boundaries.</update>

Mind you, some people hate using constants, but that's another story.

--
g r i n d e r

Replies are listed 'Best First'.
(tye)Re: use constant hash keys
by tye (Sage) on Jul 31, 2001 at 00:34 UTC

    Actually, there are lots of ways to write that. Any expression that isn't just a bareword would work. $g{FOO()} is what I would have used. I've seen others write $g{&FOO} but I consider that a bad habbit as there are cases where that will get you into trouble.

    You can also declare scalars as being constant (read-only) such as with ex::constant::vars (I seem to recall a more recent module that also does this, but a few quick searches didn't turn it up). These either toggle an internal bit that tells Perl to throw an error if you try to modify the value, or tie the variable to a package that does something similar. The first is a nice, clean way to make constants but it requires C code in the module.

    Barewords are rather ambiguous in Perl and so how they get interpretted depends on a lot of things. So normally you avoid barewords. But, if you can arrange to have a bunch of subroutines predeclared, then invoking those via barewords will catch typos at compile time.

    It'd be cool if you could get use strict to (optionally) treat subroutines more like it does variables so that a call to myRoutine() would be a compile-time error if myRoutine() hadn't already been declared. This change is very similar to what happened between old K&R C and ANSI C. Like all such strictures, it can be a pain to make happy (having to declare all subroutines before you go and use them), but it can also catch stupid mistakes must quicker, which is why strict.pm exists in the first place.

    Add to that a warning if you declare a subroutine that shares a name with a built-in, and a whole boatload of problems will be solved.

            - tye (but my friends call me "Tye")