Solutions and Discussion for Perl Quiz of the Week #5
Mark Jason Dominus <[email protected]> Tue, 19 Nov 2002 17:49:52 -0500
| Newsgroups | gmane.comp.lang.perl.qotw.quiz-of-the-week |
|---|---|
| Organization | Plover Systems |
| Message-ID | <[email protected]> |
Sample solutions and discussion
Perl Quiz of The Week #5 (20021113)
You will write a function to lay out crossword puzzles.
If you are unfamiliar with American and British style crossword
puzzles, an example is at:
http://perl.plover.com/qotw/misc/r005/puzzle.jpg
Your function, 'layout_crossword' will get an array argument which
represents the desired layout of the crossword puzzle. it will then
return a display version of the puzzle. For example, the diagram at
the URL above would be represented like this:
@sample_puzzle = qw(
....X.....X....
....X.....X....
....X.....X....
.......X.......
XXX......X.....
.....X......XXX
......X...X....
...X.......X...
....X...X......
XXX......X.....
.....X......XXX
.......X.......
....X.....X....
....X.....X....
....X.....X....
);
If given this array as argument, layout_crossword(@sample_puzzle)
would return an array containing the following 61 strings:
############################################################################
#1 #2 #3 #4 ######5 #6 #7 #8 #9 ######10 #11 #12 #13 #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
#14 # # # ######15 # # # # ######16 # # # #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
#17 # # # ######18 # # # # ######19 # # # #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
#20 # # # #21 # # ######22 # #23 # # # # #
# # # # # # # ###### # # # # # # #
# # # # # # # ###### # # # # # # #
############################################################################
################24 # # # #25 # ######26 # # # # #
################ # # # # # ###### # # # # #
################ # # # # # ###### # # # # #
############################################################################
#27 #28 #29 # # ######30 # # #31 # # ################
# # # # # ###### # # # # # ################
# # # # # ###### # # # # # ################
############################################################################
#32 # # # # #33 ######34 # # ######35 #36 #37 #38 #
# # # # # # ###### # # ###### # # # #
# # # # # # ###### # # ###### # # # #
############################################################################
#39 # # ######40 # #41 # # # #42 ######43 # # #
# # # ###### # # # # # # ###### # # #
# # # ###### # # # # # # ###### # # #
############################################################################
#44 # # #45 ######46 # # ######47 # #48 # # # #
# # # # ###### # # ###### # # # # # #
# # # # ###### # # ###### # # # # # #
############################################################################
################49 #50 # # # #51 ######52 # # # # #
################ # # # # # ###### # # # # #
################ # # # # # ###### # # # # #
############################################################################
#53 #54 #55 # # ######56 # # #57 # # ################
# # # # # ###### # # # # # ################
# # # # # ###### # # # # # ################
############################################################################
#58 # # # # #59 # ######60 # # # #61 #62 #63 #
# # # # # # # ###### # # # # # # #
# # # # # # # ###### # # # # # # #
############################################################################
#64 # # # ######65 # #66 # # ######67 # # # #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
#68 # # # ######69 # # # # ######70 # # # #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
#71 # # # ######72 # # # # ######73 # # # #
# # # # ###### # # # # ###### # # # #
# # # # ###### # # # # ###### # # # #
############################################################################
(There are 61 lines here, each 76 characters long; the function should
return a list of 61 strings, one for each line, with each string 76
characters long.)
layout_crossword() will make use of two auxiliary arrays that describe
what empty and full squares should look like. In the example above,
these arrays were:
@empty_square = qw(######
#....#
#....#
#....#
######
);
@full_square = qw(######
######
######
######
######
);
layout_crossword() must scan over the input representation, constructing
an array of strings, which it will return. It must repeatedly insert
the contents of @empty_square or @full_square into the appropriate
place in the output array, depending on whether the corresponding
character of the input was '.' (an empty square) or any other
character (a full square.)
layout_crossword() must also compute the appropriate numerals to insert
into the blank squares, and insert them into the right places in the
output. A numeral should be placed into the upper-leftmost empty part
of its square. If the numeral doesn't fit in the square, the program
should die. Again, empty parts of a square are denoted by '.'
characters.
An empty square should receive a numeral if it is the first square in
a word; words run from left to right and from top to bottom. See the
diagram for an example.
All '.' characters should be translated to real spaces on output, as
in the example.
The function should RETURN A LIST OF STRINGS, which might be printed
later. It should not print anything.
----------------------------------------------------------------
There was some confusion about the purpose of the @empty_square and
@full_square arrays. (The 'glyphs'.) When I thought up the question,
I wanted the function to get three arguments: The crossword template,
and the empty and full glyphs. But there was no good way to pass
these three arguments to a single function without using references,
and the rules of the game say that the regular quiz must be soluble
using only the techniques explained in _Learning Perl_. That means no
references. So I compromised and had the glyphs passed via to
external, global arrays.
Unfortunately, I didn't make my intentions clear enough when I posed
the problem, and a number of people thought that the glyph arrays were
private to the layout_crossword function. This was a perfectly
reasonable conclusion, since the problem statement erroneously
declared the arrays as 'my' variables. Oops! My apologies to anyone
who was confused by this. When I tested the programs that were sent
to the qotw-discuss list, I hacked them all to use global glyph
arrays. I also did other minor hacking that was necessary to make the
programs work with my test harness, which such hacking suggested
itself.
Three programs consistently produced the best-looking output: Ron
Isaacson's, which was by far the best, Alex Lewin's, and mine. But
mjd1.pl was 43% shorter than lewin.pl and 51% shorter than
isaacson.pl, so that's the one I'll discuss in detail. (Also, both
isaacson.pl and lewin.pl use references, and lewin.pl is in
object-oriented style, and so outside the scope of _Learning Perl_.)
There are two small utility subroutines:
sub add_numeral {
my ($n, @sq) = @_;
my $space = '\.' x length($n);
for (@sq) {
return @sq if s/$space/$n/;
}
die "Square was too small for numeral $n\n";
}
Here $n is a numeral, and @sq is a glyph array. add_numeral() inserts
the numeral $n into the glyph array and returns the result. It first
assembles a regular expression that looks for L dots in a row, where L
is the length of $n. Then it scans the glyph from top to bottom,
looking for the L dots, which represent a space large enough to hold
the numeral. When it finds one, it replaces the dots with the numeral
and returns the result. If there is no place to put the numeral, it
dies.
One of the tests I ran used an 'empty' glyph that looked like this:
######
## ##
# #
# #
######
A correct program will insert the numeral into the top space like this:
###### ######
##7 ## ##17##
# # # #
# # # #
###### ######
Several of the less-correct programs assumed that the empty glyphs
would be completely empty inside their borders, and produced outputs
like these:
###### ######
#7 ## #17 #
# # # #
# # # #
###### ######
The other utility subroutine just gets an X and Y coordinate and the
puzzle template, and returns true if the corresponding square is
empty.
sub is_blank {
my ($x, $y, @puzzle) = @_;
return if $y < 0 || $x < 0
|| $y >= @puzzle || $x > length($puzzle[0]);
return substr($puzzle[$y], $x, 1) eq ".";
}
The reason this is here is to establish the convention that squares
outside the puzzle are considered to be full, not empty. This
simplifies the process of determining whether an empty square should
receive a numeral. The complete rule for deciding whether a square
gets a numeral is that a blank square gets a numeral if the square
above is full, if the square to the left is full, if it is in the top
row, or if it is in the leftmost column. By adopting the convention
that squares outside the diagram are considered full, we can simplify
the logic for numbering squares: A square gets a numeral if the square
above or to the left is full.
The main function is fairly straightforward. It loops over the rows
from top to bottom, and over the squares in each row from left to
right. It sets '@square' to an appropriate glyph for the current
square, copying it from @full_square or @empty_square as appropriate,
and then, if empty, it uses add_numeral() to add a numeral to @square
if the square above or to the left is full.
use strict;
our (@empty_square, @full_square);
sub layout_crossword {
my @puzzle = @_;
my $N = 1;
my @result;
my ($h, $w) = (scalar(@puzzle), length($puzzle[0]));
for my $y (0 .. $h-1) {
my @row;
for my $x (0 .. $w-1) {
my @square;
my $blank = is_blank($x, $y, @puzzle);
if ($blank) {
@square = @empty_square;
unless (is_blank($x-1, $y, @puzzle)
&& is_blank($x, $y-1, @puzzle)) {
@square = add_numeral($N++, @square);
}
} else {
@square = @full_square;
}
Now there's the interesting question of what to do with the
overlapping parts of adjacent squares. This program uses an extremely
simple strategy: It trims off the left-hand edge of the square, so that:
+----+ ----+ and ###### #####
| | becomes | # # becomes #
| | | # # #
+----+ ----+ ###### #####
Now the current square can borrow the right-hand edge of the square
to its left. Squares in the leftmost column have nobody to borrow
from, so the trimming is not performed for those squares:
# trim off overlap with square to left
if ($x > 0) {
s/^.// for @square;
}
Then we similarly trim off the topmost edge of each square, except for
those in the topmost row of the diagram:
# Now trim off overlap with square above
if ($y > 0) {
shift @square;
}
Now that the square is complete, we append it to the right-hand end of
the current row of the output:
# add square to output
for (0 .. $#square) {
$row[$_] .= $square[$_];
}
}
When we finish a row, we turn the dots into spaces, as required by the
spec, and insert the row into the return value array. When we
finish the last row, we return the array:
for (@row) {
tr/./ /;
}
push @result, @row;
}
@result;
}
----------------------------------------------------------------
Notes:
1. The test data and the test results are at
http://perl.plover.com/qotw/misc/r005/
The program above is
http://perl.plover.com/qotw/misc/r005/mjd1.pl
The subdirectory 'templates' contains four sample crossword
templates. The subdirectory 'glyphs' contains ten pairs of glyphs.
The test harness, TestXWord.pm, tries the function on each
combination of templates and glyphs, and deposits the output into
the 'output' directory. To use it, say
perl -MTestXWord yourprogram.pl < /dev/null
The 'check-results' program checks the outputs against the sample
results in the 'standard' directory. Files in the 'standard'
directory have names of the form
PUZZLE-glyphset-##.x
where ## is a number of points and 'x' is an arbitrary letter. If
a program's output matches this file, it is awarded that many
points. If an output doesn't match any of the 'standard' forms, it
is copied to the 'mismatches' directory. I went over 'mismatches'
repeatedly and copied all the 'mismatches' that actually looked
good into the 'standards' directory.
2. The technique I used to abutting the squares is very simple, and
produces good-looking output most of the time. For some examples,
it is not so good. One of the test glyph sets, 'mixed', contains
mismatched glyphs:
+----+ ######
|....| ######
|....| ######
|....| ######
+----+ ######
With these glyphs, the asymmetry in my algorithm becomes obvious:
######----+----+----+#####
######1 |2 |3 |#####
###### | | |#####
###### | | |#####
######----+----+----+#####
######4 | | |5 |
###### | | | |
###### | | | |
######----+----+----+----+
|6 | |#####7 | |
| | |##### | |
| | |##### | |
+----+----+#####----+----+
|8 | |9 | |#####
| | | | |#####
| | | | |#####
+----+----+----+----+#####
######10 | | |#####
###### | | |#####
###### | | |#####
######----+----+----+#####
When there's a disagreement between two adjoining cells about what
their shared property should look like, the cell above or the cell
to the left always wins.
3. Peter Haworth's program does better with the mixed glyphs, and is
also very small. I thought that getting this exactly right would
be a big pain. I even wrote code do to it, and then decided to
leave it out of the question because it was too much code. Peter
cuts the Gordian Knot here and uses a very simple method.
Peter's program starts by filling the entire grid with full-square
glyphs, and then superimposes the empty-square glyphs on top of
those. Empty squares win whenever there is a disagreement about
the appearance of shared territory. For the template above, his
program generates this output:
#####+----+----+----+#####
#####|1 |2 |3 |#####
#####| | | |#####
#####| | | |#####
#####+----+----+----+----+
#####|4 | | |5 |
#####| | | | |
#####| | | | |
+----+----+----+----+----+
|6 | |####|7 | |
| | |####| | |
| | |####| | |
+----+----+----+----+----+
|8 | |9 | |#####
| | | | |#####
| | | | |#####
+----+----+----+----+#####
#####|10 | | |#####
#####| | | |#####
#####| | | |#####
#####+----+----+----+#####
The difference is subtle, but I think it is much handsomer.
4. The algorithm I used to determine whether a square should receive a
numeral fails in certain cases. Consider this template:
.....
.#.#.
.....
.#.#.
.....
My program generates this output: But it should be:
##################### #####################
#1 #2 #3 #4 #5 # #1 # #2 # #3 #
# # # # # # # # # # # #
##################### #####################
#6 #####7 #####8 # # ##### ##### #
# ##### ##### # # ##### ##### #
##################### #####################
#9 #10 # #11 # # #5 # # # # #
# # # # # # # # # # # #
##################### #####################
#12 #####13 #####14 # # ##### ##### #
# ##### ##### # # ##### ##### #
##################### #####################
#15 #16 # #17 # # #6 # # # # #
# # # # # # # # # # # #
##################### #####################
The problem here is that I generate a numeral for any empty square
below (or to the right of) a full one, but it's only correct to
generate a numeral for an empty square below (or to the right of) a
full one that is not also above (or to the left of) a full one. If
there are full squares on both sides, the numeral is inappropriate
because there is nowhere for the word to go.
In American-style crossword puzzles, this situation can never occur,
because there is an express prohibition on exactly this situation.
Every blank square must be at the intersection of two words, one
across and one down. A square that is part of only a single word
is called an 'unkeyed square' and is strictly forbidden.
However, in many British-style crossword puzzles, most famously the
London Times Sunday puzzle, there are unkeyed letters. I
specifically mentioned "British style crossword puzzles" in the
question, so this is a defect. To fix it, change
unless (is_blank($x-1, $y, @puzzle) && is_blank($x, $y-1, @puzzle)) {
@square = add_numeral($N++, @square);
}
to
if (!is_blank($x-1, $y, @puzzle) && is_blank($x+1, $y, @puzzle)
||!is_blank($x, $y-1, @puzzle) && is_blank($x, $y+1, @puzzle)) {
@square = add_numeral($N++, @square);
}
I forgot all about this until I looked closely at Peter Haworth's
contribution, which gets it right. (Peter, of course, is a Brit.)
5. ensch2.pl is a peculiar case. Faced with the problem of how to
overlap adjacent glyphs, Peter B. Ensch did something interesting.
His program ensch2.pl inserted backspace characters between the
glyphs:
+---+^H+---+^H
| |^H| |^H
+---+^H+---+^H
(I have represented the backspaces by '^H'). I didn't realize this
at first. When I went to look at the program's output, I used the
'less' pager program, which interpreted the ^H's as requests to
overstrike! So when I used the pager, I got what looked like
+---+---
| |
+---+---
but with the middle vertical line in boldface. When I just printed
the output to the terminal with 'cat', it looked normal. But to
the automatic test suite, the answers looked completely wrong.
The test suite hated it, but if the backspacing is allowed,
ensch2.pl would be one of the better performers.
6. One common problem was programs that assumed that the glyphs would
have a certain appearance, or would be a certain size. Using the
glyphs
## and ..
## ..
caused problems for many peoples' programs, which could not figure
out how to fit the numerals in, or which assumed the presence of a
border. The example program above completely botches these
examples because it insists on overlapping the adjacent glyphs,
even though that means stripping out 3/4 of each glyph. For small
glyphs, overlapping is a mistake. Ron Isaacson's program was the
only one posted on the qotw-discuss list that handled this case
properly at all. His program overlaps squares only if the empty
and full square borders match. This leads to cluttered but
reasonable behavior in the 'mixed' case above:
######+----++----++----+######
######|1 ||2 ||3 |######
######| || || |######
######| || || |######
######+----++----++----+######
######+----++----++----++----+
######|4 || || ||5 |
######| || || || |
######| || || || |
######+----++----++----++----+
+----++----+######+----++----+
|6 || |######|7 || |
| || |######| || |
| || |######| || |
+----++----+######+----++----+
+----++----++----++----+######
|8 || ||9 || |######
| || || || |######
| || || || |######
+----++----++----++----+######
######+----++----++----+######
######|10 || || |######
######| || || |######
######| || || |######
######+----++----++----+######
and perfect behavior in the very-small-glyph case:
##1 2 3 ##
## ##
##4 5
##
6 ##7
##
8 9 ##
##
##10 ##
## ##
7. There were a few oddities that caused some programs to appear to
perform more poorly in the tests than was actually warranted. The
program sainio.pl used a global variable to store the current clue
number, and never reset it between calls to layout_crossword().
Since the test harness called layout_crossword() forty times in a
row, the numbers grew to be four digits long and then wouldn't fit
into the boxes any more. sainio2.pl is a corrected version.
schmidt.pl and schmidt2.pl copied the glyphs into two private
arrays, @UL_empty_square and @UL_full_square. Unfortunately, they
did so at compile time, thus foreclosing the possibility that
anyone could change the glyphs later, and preventing the test
harness from changing the glyphs. With this defect repaired, these
two programs did well in the testing.
As I mentioned above, a better design for this function would be
for it to have three arguments instead of using the two global
glyph arrays. jones2.pm did do this, so it failed the tests. I
hacked it so that it used the specified argument format instead.
But the output was quite broken! It didn't use the correct glyphs,
and it only numbered the 'across' clues!
++++++++++++++++
+##+1 + + +##+
+##+ + + +##+
+##+ + + +##+
++++++++++++++++
+##+2 + + + +
+##+ + + + +
+##+ + + + +
++++++++++++++++
+3 + +##+4 + +
+ + +##+ + +
+ + +##+ + +
++++++++++++++++
+5 + + + +##+
+ + + + +##+
+ + + + +##+
++++++++++++++++
+##+6 + + +##+
+##+ + + +##+
+##+ + + +##+
++++++++++++++++
wolters.pl produced some reasonable-looking outputs, but did not
translate the dots to spaces, so the results looked like:
+----+----+----+----+----+
|####|1...|2...|3...|####|
|####|....|....|....|####|
|####|....|....|....|####|
+----+----+----+----+----+
|####|4...|....|....|5...|
|####|....|....|....|....|
|####|....|....|....|....|
+----+----+----+----+----+
|6...|....|####|7...|....|
|....|....|####|....|....|
|....|....|####|....|....|
+----+----+----+----+----+
|8...|....|9...|....|####|
|....|....|....|....|####|
|....|....|....|....|####|
+----+----+----+----+----+
|####|10..|....|....|####|
|####|....|....|....|####|
|####|....|....|....|####|
+----+----+----+----+----+
I added the line
tr/./ / for @puzzle;
just before the return from the function. However, it didn't
correctly handle different-sized glyphs.
8. I got the idea for this problem from _The Art of Computer
Programming, Vol. 1: Fundamental Algorithms_, by Donald E. Knuth.
(In the 3rd edition, it is problem 1.3.2.23, and is on page 163.)
I remembered that there was some issue in the Knuth problem that
made it more difficult than the problem I was posing, but I didn't
remember what is was, and I didn't look it up until just now.
The Knuth version of the puzzle says that black squares at the
border of the puzzle should be deleted from the output. His
example: If the input was
#....#
..#...
....#.
.#....
...#..
#....#
Then the output should be
+++++++++++++++++++++
+01 + +02 +03 +
+ + + + +
+++++++++++++++++++++++++++++++
+04 + ++++++05 + +06 +
+ + ++++++ + + +
+++++++++++++++++++++++++++++++
+07 + +08 + ++++++ +
+ + + + ++++++ +
+++++++++++++++++++++++++++++++
+ ++++++09 + +10 + +
+ ++++++ + + + +
+++++++++++++++++++++++++++++++
+11 +12 + ++++++13 + +
+ + + ++++++ + +
+++++++++++++++++++++++++++++++
+14 + + + +
+ + + + +
+++++++++++++++++++++
Notice how the corner black squares have vanished. If there were
other black squares next to these, they would vanish also. Knuth
says: "The diagram... might have long paths of black squares that
are connected to the outside in strange ways."
Although my version of the problem was missing this complication,
it had an additional complication because the full and empty square
glyphs were variable instead of fixed. My problem specification
didn't provide much guidance about how to make the glyphs overlap,
and in the case where the edges of the two glyphs didn't match, it
wasn't immediately clear how to overlap them and still make the
result look good.
9. It was pointed out on the -discuss list that the code I posted
yields a warning, if warnings are enabled. Specifically
my @empty_square = qw(######
#....#
#....#
#....#
######
);
yields the warning "Possible attempt to put comments in qw() list".
One poster to the list said:
Seeing as much of the Perl community have been trying to get
new Perl programmers to turn on warnings and strict, in an
effort to highlight problems with their code, I have been
surprised to see MJD's quiz this week. In order to use the
empty_square and full_square arrays, as included in the quiz
text, you are actually inclining people to turn off strict and
warnings. Which IMHO is not good.
http://perl.plover.com/~alias/list.cgi?1:mss:605
This remark, unfortunately, comes right at the intersection of
several philosophical stances I hold, and that makes me very
cranky.
First, the warning has nothing whatever to do with 'strict'.
Throughout this message, the author says "warnings and strict",
"strict and warnings", as if in one breath. The code in question
is completely strict-safe. (It *shouldn't* be, but that is an
unrelated matter.) Why mention 'strict' at all?
The Perl community has become increasingly dogmatic in the past few
years about the use of 'strict'. It is common to see people ask
questions in newsgroups, and to post four-line examples, and be
criticized for failing to use 'strict'. "Why aren't you using
'strict'?" people ask. Well, because it is a four-line example
posted in a Usenet article, obviously. 'strict' has no value in
such cases, except perhaps to get people to shut up about it.
It is true that the Perl community has been trying to get new Perl
programmers to turn on warnings and strict. I have no objection
to this. What I do object to is that the community seems to be
trying to get people to turn on warnings and strict without knowing
why they are doing that, or what they are for.
It is common to see people ask questions in newsgroups like this:
I got the error "Global symbol "$z" requires explicit package
name." What does that mean?
This is like someone coming to say that there is a loud bell
ringing in the hallway, and what should be done about it? Of
course, it is the fire alarm. They were told to always turn the
fire alarm on, but nobody told them what it would mean if it began
to ring.
I believe that one of the biggest problems with programming as a
profession is that programmers are fearful and superstitious.
Programming is only about sixty years old. When chemistry was
sixty years old, practitioners were trying to turn lead into gold,
to extract the essence of fire, and so forth. After a few hundred
years they learned a little more and began to study phlogiston. So
we are in the dark ages of programming, and we live in a dangerous
world that we do understand only poorly. Many people respond to
this with superstition: "Always use objects." "Never use a global
variable." "Perl is better than Python." "Always use strict."
We do not have to give in to this superstition. We don't have to
say "To be safe, always use strict. And to be double safe, throw
salt over your left shoulder." We are engineers, and programming
is empirical. We should by all means encourage beginners to use
the best possible engineering practices. But we should not
encourage the blind use of certain programming features.
When a person says "warnings and strict" four times, when talking
about a piece of code that emits a warning but is strict-safe, what
is going on? Clearly this person is not thinking about the meaning
of what he is saying. The code is also not ISO 9000 compliant; why
not mention that also? I think this is a superstitious effect.
Instead of looking at the reality of the situation, which is that
the code raises certain specific warnings, and the warnings have a
certain specific meaning, which suggests that the code should be
examined for certain specific problems, the speaker has lumped all
warnings and diagnostics together in one group and adopted the
stance that all such warnings should be eliminated. This shows a
lack of understanding.
I think almost anyone who says "always use strict" is suffering
from this lack of understanding. "strict" is not one but three
features, and none of these three features has anything at all to
do with the other two. Saying "always use strict" is like saying
"always use a hammer, a screwdriver, and a drill." For some
projects, perhaps only the hammer and drill are appropriate, and
the screwdriver is an irrelevant distraction. So it is too with
"strict". People are being encouraged to load up with tools that
they don't know how to use.
The results of this are sometimes stunningly silly. I have many
examples of programs that start by saying:
use strict;
my ($rounds, $round_temp, $squares, $page, $x, $y, $z, $cell,
$player_move, @available_choices, $computer_move, @choices,
$round, $winner, $player_move_pretty, $computer_move_pretty);
my ($round_minus_one);
The programmer here wants to use global variables; she does not
understand what lexical variables or for, or why they are
preferred. But, at the advice of some well-meaning person, she has
put 'use strict' at the top of the program, and now global
variables are forbidden. So she declares every variable at the top
of the program, effectively making them all global, and getting
none of the encapsulation, reuse, or maintenance benefits that
lexical variables are supposed to accrue. Another example:
my @ret=eval "layout_tree_$format(\$tree)";
Why do this? There is a safer and more efficient method:
my @ret= "layout_tree_$format"->($tree);
Perhaps the programmer didn't know about the safer and more
efficient method. Or perhaps he avoids it, as many people do,
because it causes a 'strict refs' failure, while the 'eval' method,
although inferior in every way, does not.
I don't think we need to do more to encourage people to
usewarningsandstrict. I think we need to do more to encourage them
to understand the warnings they get and to take appropriate action.
When I teach programming classes, I am always astonished at how
little attention the students pay to the error messages they
receive. The compiler complains of a syntax error on line 197, and
the programmer's response is not to look at line 197, but to
eyeball a random portion of the program in the hope that the error
is there. By encouraging people to "always use strict and
warnings" and to think of diagnostic messages as bad, and something
to avoid, we are doing the exact wrong thing. The right thing is
to encourage people to pay attention to the messages, to try to
understand them, and then to make considered judgements about what
they mean. That is what I think beginners need to learn.
In this case, the warning is saying "Possible attempt to put
comments in qw() list". What does that mean? It means that perl
has seen a # sign in a qw(), and it is afraid that I might be
trying to write something like this:
my @array = qw( red crimson # But not scarlet
blue azure
green
);
Here the thing that looks like a comment is not a comment; instead,
the @array gets nine elements, including 'But', 'not', 'scarlet',
and '#'. It is good that perl warns us about this.
In my example code, however, this is not the case:
my @empty_square = qw(######
#....#
#....#
#....#
######
);
I am *not* trying to put a comment into a qw() list. Perl sees the
'#' signs, and it is afraid that I *might* be doing that, so it
warns me. But it is mistaken; the # signs are doing what I want
here. In such a case, it is perfectly appropriate to ignore the
warning. The compiler has had its say, and I have listened to it,
but it is just a machine, and I know better than it does what I
want. If you are troubled by the warning message itself, the
correct approach here is NOT to code around it by writing something
like this:
my @empty_square = ('######',
'#....#',
'#....#',
'#....#',
'######',
);
The correct response is to SHUT OFF THE WARNING:
my @empty_square;
{ no warnings 'qw';
@empty_square = qw(######
#....#
#....#
#....#
######
);
}
(The "no warnings 'qw'" declaration shuts off only those warnings
that pertain to the qw() operator, and only inside that one block.
Elsewhere, all warnings will still be issued. Inside the block,
all other warnings will still be issued.)
The thing that really irks me about the 'strict' dogmatism is how
defective is most of the dialog about it. Last year I read a
review of a book about using Perl to write CGI programs. The
reviewer harshly criticized the author for not having used
'strict'. The reviewer did not say which of the three parts of
'strict' would have been valuable. His opinion was apparently that
all programs should use 'strict', whether it would be valuable or
not. I objected, pointing out that none of the programs in the
book used references, so that 'strict refs' would not be doing
anything; that none of the example programs were more than twenty
lines long, so there was no practical difference between global and
lexical variables, and hence no reason to use 'strict vars' to
forbid global variables; and that the only value of 'strict subs'
is to prevent future maintenance problems when someone adds a
subroutine whose name is the same as what was previously a
bareword, a feature of small value at best and of less value in
these tiny example programs.
But the reviewer did not address any of my specific technical
points. Instead, he told an anecdote about a bad programmer, and
said that we should teach everyone "good programming style" right
from the start. That begs the question of what "good programming
style" is. I realized then that the reason for our disagreement
was that my idea of good programming style was motivated by what
was useful and effective, whereas his was motivated by
superstition. Considerations of usefulness did not come into play.
That is my opinion on "use warnings and strict". The short version
is: No, I do not believe there is any inherent value in "keeping
warnings and strict happy", and I am going to continue to try to do
the most appropriate thing for the circumstances. I believe that
that is the only way to set the best possible example for
beginners.
Sorry to go on so long, but this has all been seething inside me
for a long time.
New quizzes tomorrow. My grateful thanks to everyone who participated
in the discussion, and also to those who quietly worked the problemns
on their own.