Constant Amusement

                                    Joseph Brenner
                                    October 26, 2004
These are the slides for a lightning talk presented at the San Francisco Perl Mongers meeting on Tuesday October 26, 2004.

(Some people need power point, me, I just need <PRE> tags and interfile html jumps.)

Table of Contents

                                 NEXT:The Problem 

the problem

Once upon a time I was working at a place where the web site code had settings centralized in a file of constants, something like this:

     package Bozotech::Definitions;

     [... skipping Exporter stuff for now ...]

     use constant BOZO_DEBUG => 0;
     use constant BOZO_VERBOSE => BOZO_DEBUG;
     use constant BOZO_NAG_MAIL => '';
     use constant BOZO_HOME => $ENV{'HOME'};
     use constant BOZO_TIME => scalar localtime;    
     use constant BOZO_COMPANY_NAME => "BozoTech, Ltd. (Very)";
     use constant BOZO_COMPANY_SLOGAN => "The *other* kind of pointy hair.";
     use constant BOZO_URL_URI_WHATEVER => "";
     use constant BOZO_MAIN_DB => "";
     use constant BOZO_TEST_DB => "";
Developers were expected to remember to modify this file after pulling a new tree out of version control. In the early days it could be Very Bad if you forgot to do this (e.g. you might use the production DB instead of a test DB), but even after fixing the major gotchas there was still some potential for embarrassment (e.g. your code might spam the entire development team with error messages).

No programmer, of course, can look at a repetitive task like this without thinking about automating it...
                           NEXT: Brute force pattern substitution

Brute force pattern substitution

First thought: ye olde s///; The "use constant" lines have a fairly regular layout, so it's not that hard to just brute force it: slurp in the file and run a stack of s{}{}msx changes on it. But when you start trying to make it insensitive to minor variations in layout, the code starts getting like this:

  $slurpie =~ 
    s{^ \s* use \s+ constant \s+ BOZO_NAG_EMAIL \s+ => \s+ (.*?) \s* ; }
     {use constant BOZO_NAG_EMAIL => ''; }msx;

or worse, if you want to preserve all the variations in whitespace:
  $slurpie =~ 
    s{^ (\s* use \s+ constant \s+) BOZO_NAG_EMAIL (\s+ => \s+) (.*?) (\s* ;) }
                                      NEXT: Run Away

run away

This was looking pretty ugly to me, and a little alarm bell went off in my head: I was hand-crafting regexps to parse a known data format. No experienced programmer does that if it can be avoided (I'm sure no one here has ever fallen into the trap of writing your own HTML parsing code, but I've heard tell of people wasting a lot of time on that).
The Right Thing to do is to look for existing code to cover the problem. But as the slogan goes "the only thing that can parse perl is perl".
                       NEXT: Parsing Perl with Perl

Parsing perl with perl

So why not use perl? I could slurp in this file, and just eval it, and... then what? Can you get access to the names of constants programmatically?
Yes: the documentation for is excellent and it explains how in detail. All you need to know is that there's a %constant::declared hash with entries for each constant.

  my @initial_constant_names = (keys %constant::declared);
  require "$definitions_file";  # easier than doing it with "eval"
  my @final_constant_names = (keys %constant::declared);
  # Get the set difference (straight out of the Cookbook, recipe 4.8)
  my (%seen, @new_constant_names);
  @seen{ @initial_constant_names } = ();
  foreach my $item ( @final_constant_names ) { 
    push(@new_constant_names, $item) unless exists $seen{$item};
  print "newly loaded constants:" . join (' ', @new_constant_names) . "\n"; 

So, I can check the current state of the constants, eval the Definitions, then check them again. The difference between the two sets is then a listing of the names of all the constants in Then I can write out a *new* version of the file with some of the old values over-ridden, set by the script to the safe values.
          NEXT: The Missing Piece of the Puzzle

the missing piece of the puzzle

But to do that, I also need to get the values of the constants that were just defined, all I've got thus far is the names of the constants. A check of the docs showed no hint of how to do that. Maybe it was so obvious that they didn't think to mention it.
                                  NEXT: Pop Quiz

pop quiz

Pop quiz: You've got the name of a constant in a variable, you want to get the value of it. How?
                                  NEXT: Solution



   $name_of_constant = 'BOZOTECH_NAG_EMAIL';

   { no strict 'refs';
       # Method one:
       $value = &{ $name_of_constant };
       # Method two:  
       $value = $name_of_constant->(); 

print "Send nag email to: $value\n";


Perl constants are just subroutines that return a constant value, remember?

Great now I can put these neat little tricks together and solve the problem!

                        NEXT: other troubles

other troubles


But... what about...

Sometimes the way a constant is defined is significant.

constants can be defined in terms of each other;
use constant HONK_LEVEL => 2 * DEBUG_LEVEL;

An interesting trick I learned recently:
use constant DEBUG => $ENV{ BOZO_BUG };

And what about the comments? You don't want to just throw them away. You might want to make some manual changes later, and the comments could be your only guide:
  use constant BOZO_VERIFY => 0;  # Watch it!
 # This will NOT do what you think it does.  *Honk* *honk*


                               NEXT: Conclusion


Taken together, those two points make it obvious that this was a total waste of time, and I was on the right track in the first place. As I should have known:

      Sometimes looks are more important than values.

                  NEXT: Introduction


Perl constants are yet another feature of the language which were hacked-in in an interesting way.
  use constant DEBUG => 1;
Uses the standard package to create a subroutine called DEBUG that just returns the constant value "1". So constants are "really" subroutines; but that gets into some interesting philosphical issues about what perl "really" is, because when you're actually running the code, that subroutine isn't there any more. The optimizer converts it into an in-line value (and also does "constant folding"). So what perl really is is a set of behaviors that the perl-porters promise to fake, a kind of consensual hallucination...
Anyway, while constants are a relatively minor feature, they do have their advantages:
  1. they're constant: one less thing you need to check when debugging
  2. constant folding: hypothetically an efficiency gain
They also have some disadvantages:
  1. they're constant: there is no "no strict 'constants';"
  2. they don't really interpolate. Best you can do:
    print "constant value: @{[ CONSTANT ]}";
  3. there's no way to set a constant from a command-line parameter
Really, the disadvantages probably outweigh the advantages.
                  NEXT: Appendix: Automatic export of all constants

Appendix: Automatic export of all constants

One result of having wasted some time thinking about these issues is that I now do things like this with my "Definitions" files:
   # Automatically export all constants defined in this file
   require Exporter;
   our @ISA = qw(Exporter);
   our @export_list;
   sub BEGIN {
     my $filename = (caller)[1];
     open ME, "<$filename" or die "Can't open $filename for input: $!";
       # get constant names from "use constant" lines
       if ( m{^ \s* use \s+ constant \s+ (.*?) \s }x ) { 
         push @export_list, $1;
   # Additional items to export (add manually)
   push @export_list, qw(  
   our %EXPORT_TAGS = ( 'all' => \@export_list );
   our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
So when you create a new definition, you don't need to remember to add a name to your @EXPORT_OK.

But really, these days when I have a choice I tend to just use upper-case variables (e.g. $DEBUG) rather than a constant, so I use a pattern like this instead:
    if ( m{^ \s* our \s* ( \$[A-Z0-9_]+ ) \s* = }x ) {  #  $UPPER_CASE vars  
                     NEXT: Appendix 2 -- Some known problems

Appendix 2: Some known problems

Some quotes from Tom Phoenix's documentation:
    Unlike constants in some languages, these cannot be overridden on the
    command line or via environment variables.
But keep an eye on Devel::Constants (chromatic is looking into variable constants...).

And things like this, these are the reasons why no one in their right mind uses perl constants:
    You can get into trouble if you use constants in a context which
    automatically quotes barewords (as is true for any subroutine call). For
    example, you can't say $hash{CONSTANT} because "CONSTANT" will be
    interpreted as a string. Use $hash{CONSTANT()} or $hash{+CONSTANT} to
    prevent the bareword quoting mechanism from kicking in. Similarly, since
    the "=>" operator quotes a bareword immediately to its left, you have to
    say "CONSTANT() => 'value'" (or simply use a comma in place of the big
    arrow) instead of "CONSTANT => 'value'".

                        NEXT: Comments from the audience

Comments from the audience

Rich Morin argued that (1) it was a bad idea to use code in a configuration file, and (2) it's a good idea to use a standard data format, e.g. YAML.
My take: I definitely agree that you shouldn't go around inventing data formats in this day and age, and choosing a standard syntax like YAML is sensible. It certainly makes the problem under discussion here -- programmatic modification of a config file -- much easier.

However, I'm not entirely presuaded that it's a bad idea to use code in a configuration file. I think there's probably a trade-off between putting the flexibility inside of the config file, and pushing it outside into a configuration tool, and off-hand I don't know why the latter would necessarily be better (but maybe my brain has been damaged by years of emacs use).

In any case, using a perl file for configuration of perl code is always going to be a temptation, because by definition it's a format that all the programmers working on the project will understand.

My contention that "the problem with constants is that they're really constant" produced some general discussion. A big faction seemed to think that this was a silly point ("You want to vary it? Then use a variable!")
The idea here is that while restrictions on what a programmer is allowed to do can often be useful for catching problems, the general perl philosphy toward these things is that these restrictions should be advisory, not hard laws. We use use strict; more often than not, but we can shut if off locally (e.g. no strict 'refs'; used in an example above) when we want to. Usually strict is very good at catching mistakes, but sometimes you're doing something weird on purpose, and you want perl to keep going even though you've done something that normally would probably be an error.

So, a constant can in theory be useful for eliminating one source of trouble (e.g. if you've got PI defined as 3.1416, you can just check it once, you don't need to worry that some broken code might have munged the value). But every once in a great while you might run into a reason where it's useful to override that behavior ("Hm, could it be that in this routine I need to use pi specified to a few more decimal places? Let's try that for a minute and see if it effects the results.")

In general, the question "how do I vary my constants?" seems entirely consistent with the perl way of thinking about things. Rules are good, but being able to break rules is also good.

There was also some general discussion of how you might get this "changeable constant" behavior. It had seemed to me that there's probably some typeglob trick that could be used to re-define the meaning of a sub in the symbol table, though my few experiments with this didn't work.

Probably the best idea that was brought up was to use a tied scaler, with some sort of freeze/thaw interface.

But then, you'd have to care a lot about it to want to hassle with that. (I was a little suprised that people took this topic as seriously as they did.)

                              Back to the TOP 

Joseph Brenner, 26 Oct 2004