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.