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
NEXT:The Problem
the problem
NEXT
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:
|
Bozotech/Definitions.pm:
package Bozotech::Definitions;
[... skipping Exporter stuff for now ...]
use constant BOZO_DEBUG => 0;
use constant BOZO_VERBOSE => BOZO_DEBUG;
use constant BOZO_NAG_MAIL => 'rednose@bozotech.com';
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 => "www.bozotech.com";
use constant BOZO_MAIN_DB => "access.bozotech.com";
use constant BOZO_TEST_DB => "flatfile.bozotech.com";
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
NEXT
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 => 'doom@kzsu.stanford.edu'; }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* ;) }
{$1BOZO_NAG_EMAIL$2'doom@kzsu.stanford.edu'$4}msx;
NEXT: Run Away
run away
NEXT
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
NEXT
So why not use perl? I could slurp in this
Definitions.pm file, and just eval it, and...
then what? Can you get access to the names of constants
programmatically?
|
Yes: the documentation for
contants.pm 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 Definitions.pm.
Then I can write out a *new* version of the Definitions.pm
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
NEXT
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
NEXT
Pop quiz: You've got the name of a constant in a
variable, you want to get the value of it. How?
|
NEXT: Solution
solution
NEXT
$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";
=> rednose@bozotech.com
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
NEXT
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
conclusion
NEXT
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
Introduction
NEXT
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 constant.pm 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:
- they're constant: one less thing you need to check when debugging
- constant folding: hypothetically an efficiency gain
|
They also have some disadvantages:
- they're constant: there is no "no strict 'constants';"
- they don't really interpolate. Best you can do:
print "constant value: @{[ CONSTANT ]}";
- 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
NEXT
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: $!";
while(<ME>){
# 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
NEXT
Some quotes from Tom Phoenix's constant.pm 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