Higher-Order Perl Errata

For 1st printing (2nd printing, what?)

Last update: 23 March 2014

Listing by page number

Listing by contributor | Listing by date reported | Listing by severity

xviii

(trivial error)

"Sean Burke recorded my Ivory Tower talk and cut CDs and sent them to me and who supplied RTF-related consulting..." should read "Sean Burke recorded my Ivory Tower talk, cut CDs and sent them to me, and also supplied RTF-related consulting...".

(RJBS)

(substantive error)

The name of Luc St-Louis is misspelled as "St. Louis". Sorry, Luc!

21

(substantive error)

The line

        warn "Couldn't open directory $code: $!; skipping.\n";

should read

        warn "Couldn't open directory $top: $!; skipping.\n";

(The same error also appears on page 24 in the first printing.)

(JPL)

(trivial error)

In the last clause of the second paragraph, the phrase "all it knows is that is should..." should read "all it knows is that it should...".

(KS)

24

(substantive error)

The line

        warn "Couldn't open directory $code: $!; skipping.\n";

should read

        warn "Couldn't open directory $top: $!; skipping.\n";

(The same error also appears on page 21.)

(LSL)

(trivial error)

In the middle display, there is a space missing between push and @results, and there is a space missing before the colon in the final return statement.

(JK)

28

(trivial error)

In the second display, the closing ] is indented too far.

(JK)

32

(minor error)

The second boldface line in promote_if() is missing a parenthesis; it says

        if ($is_interesting->($element->{_tag}) {

but it should be

        if ($is_interesting->($element->{_tag})) {

(JCZ)

32-33

(trivial error)

The last code display on page 32, and the first on page 33, use tags 'maybe', which is inconsistent with the all-capitals tags used on page 30-32. Although I think that the code will still run as given, there was no reason to change the tags; it was just an oversight.

A similar inconsistency appears on pages 326-328.

33

(substantive error)

The call to promote_if() at the top of the page has:

        promote_if(
        sub { $_[0] eq 'h1' },
        $_[0])

but it should be:

        promote_if(
        sub { $_[0] eq 'h1' },
        @_)

This erroneous code also appears at the top of page 327.

(JCZ)

34

(minor error)

The definition of the Fibonacci function here is not the one that is usually used by mathematicians. Mathematicians always put fib(0) = 0 and fib(1) = 1, so fib(2) = 1 also. But the definition I give on page 34 has fib(0) = 1 instead, so that fib(2) = 2.

This may have been deliberate, and in any case the error is small, because page_34_fib($n) = standard_fib($n+1) for all n. But it at least shows poor judgement, because the definition I did use was inconsistent with the table on page 33, which has fib(1) = fib(2) = 1 as is standard.

If one uses the standard definition, the Fibonacci function enjoys a number of mathematical properties that make its analysis more tractable. (For example, fib($a*$n) is always an exact multiple of fib($n).)

It would have been better to use the usual definition, as I did on page 243:

        sub fib {
          my $n = shift;
          if ($n < 2) { return $n }
          fib($n-2) + fib($n-1);
        }

The nonstandard definition also appears on pages 66 and 69 and in the illustration on page 76.

(JCZ)

38-39

(substantive error)

The partition() function should have a declaration my $share_2; somewhere near the top.

(SB)

44

(trivial error)

The message "Couldn't chdir to '$dir: $!; aborting" should read "Couldn't chdir to '$dir': $!; aborting".

(HDP)

(trivial error)

The } at the end of change_dir() is indented too far.

(RJBS)

45

(trivial error)

There is a space missing before the => in 'DEFINE'=> \&define_config_directive,.

49

(not an error)

Note that this version of read_config() doesn't support the INCLUDE directive as defined in the previous section, because order of the arguments in the $action call is inconsistent with the order of the parameters in the read_config() function. A similar problem occurs with the version on page 50.

If you want to fix this, one way is to reorder the parameters; another is to have INCLUDE call a helper function that calls read_config() in turn with the arguments in the correct order.

(TH)

(minor error)

There is a } missing from the last line on the page.

(JPL)

53

(trivial error)

At the top of the page, the phrase "if looks for" should be "it looks for".

(TJH)

56

(trivial error)

Each of the six => items in the code display at the bottom of the page should be preceded by a space.

(PW)

57

(trivial error)

In the last display, the next-to-last } is indented too far.

(JK)

59

(minor error)

The _DEFAULT_ callback function is missing its close brace.

(JCZ)

(minor error)

In the code display in the middle of the page, the expression $table{_DEFAULT_} should be $table->{_DEFAULT_}.

(JCZ)

64

(trivial error)

In the final paragraph, the fonts in the phrase "with arguments 128,0,64" are wrong. It should appear as "with arguments (128, 0, 64)".

76

(trivial error)

In the illustration, the code for the caching stub is missing a $ sign. The second line has func->(@_), but it should have $func->(@_).

(LE)

79

(trivial error)

In the middle of the page, there is a space missing between "in" and "Figure 3.6".

84

(trivial error)

In "join $; , @_ ", the word "join" should be in monospaced font.

88

(major error)

At the bottom of the page, I assert that "Using UNIVERSAL::isa avoids all these problems." In fact, it only solves the first problem, not the second, much-less-likely one.

(KW)

94

(substantive error)

The discussion of the find_share() function gets the two arguments in the wrong order. For example, the mention of find_share([7,8,9,10], 41) should actually be find_share(41, [7,8,9,10]). Similarly, the key generation function sub {join "-", @{$_[0]}, $_[1]} should be sub {join "-", @{$_[1]}, $_[0]}.

(RJBS)

(substantive error)

The discussion of the find_share() function gets the two arguments in the wrong order. Although some of these were corrected in the second printing, one was not. Three lines before the section break, find_share([1..20], 210) should actually be find_share(210, [1..20]).

(DFC)

95

(trivial error)

In the comment in the first code display, the word computatation should be computation.

(AK)

98

(minor error)

Near the top of the page, the expression $cache{$self} is missing its close brace.

(VL)

99

(trivial error)

The second code display on the page has an extra space after the open parenthesis in the memoize_method() call.

(minor error)

The third code display on the page has a period between the arguments to the memoize_method() call; it should be a comma.

(GC)

104

(substantive error)

I said that the sort will not call chronologically on the same pair of dates; this is only true if there are no duplicate items in the input list.

(WL)

105

(trivial error)

"a sequence of <=>'s and ||s" should be "a sequence of <=>s and ||s".

(VL)

110

(major error)

The code

      sub function {
        if (++$calls > 100) { memoize 'function' }
        ...

should have been

      sub function {
        if (++$calls == 100) { memoize 'function' }
        ...

since we don't want to memoize the same function more than once.

(FF)

(minor error)

In the fourth paragraph, the phrase "the cost-benefit ratio is nine times as large" should read "the cost-benefit ratio improves ninefold".

112

(substantive error)

In the END block, the table header is written to STDERR, but the table itself is written to STDOUT. They should both be written to the same place, whatever that is.

115

(substantive error)

"the next time next is called" should read "the next time NEXTVAL is called". "The next operation is written in Perl" should read "The NEXTVAL operation is written in Perl".

120

(trivial error)

The last sentence in the second paragraph should read "Then we would still be able to emulate the original dir_walk()...".

(PW)

122

(trivial error)

There is a space missing before the => in 'all'=> \@EXPORT_OK.

(PW)

123

(substantive error)

The lines:

        if (-d $file) {
          opendir my $dh, $file or next;

don't work because the next is not inside a loop. They should be replaced with:

        if (-d $file && opendir my $dh, $file) {

(WL)

126

(trivial error)

"But they payoff" should read "But the payoff".

(JS)

(trivial error)

The last line of code on the page should be indented one more space to the right.

(RJBS)

128

(minor error)

The loop at the top of the page:

        while ($file = NEXTVAL($octopus_file)) { ... } 

will terminate prematurely if the initial call to interesting_files() includes a request to examine a file named 0. To avoid this edge case, one should write:

        while (defined($file = NEXTVAL($octopus_file))) { ... } 

instead. Similarly, the if test just below should be if (defined(NEXTVAL(...))) rather than just if (NEXTVAL(...)).

(JPL)

(minor error)

The variable $next_octopus in the second code display should have been $octopus_file, to be consistent with the first code display and with the example at the bottom of the previous page.

(JPL)

129

(substantive error)

The next-to-last paragraph says that items are removed from @items and placed onto the end of @perms. Actually the code has unshift, so they're placed at the beginning of @perms, not the end.

(VL)

130

(trivial error)

There is an extra blank line before the closing brace of the while loop in the middle of the page.

(JPL)

132-133

(substantive error)

In increment_odometer(), the line

        until ($odometer[$wheel] < 9 || $wheel < 0) {

should perform the tests in the other order:

        until ($wheel < 0 || $odometer[$wheel] < 9) {

It is not logically correct to use $wheel as an array subscript when it might be out of range. The function works as printed, but this is something of a lucky fluke.

An analogous problem is present in increment_pattern() on page 133.

135

(trivial error)

The parenthetical remark about Lincoln Stein's article is missing its close parenthesis.

(AL)

(trivial error)

The spacing in the first paragraph of section 4.3.2 is generally too tight, and in particular the words "of" and "Virginia" in the first sentence are too close together.

(VL)

(substantive error)

The phrase "DNA is organized as a sequence of base pairs..." should read "DNA is organized as two complementary sequences of nucleotides...". A "base pair" is one complementary pair of nucleotides.

136

(minor error)

The make_genes() function does not correctly handle the special case where the input contains an empty wildcard, symbolized by (). In such a case, there are no matching strings, and the iterator should immediately indicate exhaustion.

(JPL)

139

(not an error)

The code

     for my $abbrev (keys %n_expand) {
       $pat =~ s/$abbrev/($n_expand{$abbrev})/g;
     }

would have been simpler as

     while (my ($abbrev, $expand) = each %n_expand) {
       $pat =~ s/$abbrev/($expand)/g;
     }

(GC)

(trivial error)

The second sentence of section 4.3.3 says "Why would we want to this?", but it should be "Why would we want to do this?".

(JCZ)

140

(substantive error)

The FlatDB library described starting on this page uses the Iterator() function defined elsewhere in the Iterator_Utils package, so it needs to import that function. The line

         use Iterator_Utils qw(Iterator);

should be added just after the package FlatDB; declaration.

(ANON)

141

(substantive error)

There should be a chomp just after the while (<$fh>) at the very bottom of the page. Otherwise, equal queries on the rightmost field won't work properly.

(Also, similar errors on pages 144, 147 (twice) and 151; thanks John P. Linderman.)

(DL)

142

(substantive error)

It would be better if the split $self->{FIELDSEP} were split $self->{FIELDSEP}, $_, -1. Otherwise, if the input data contained empty trailing fields, the would be omitted from the @fields array, and later could might produce an "uninitialized value" warning. For the example database this can't happen, since the last field is numeric, and so won't be empty, but it could happen with other databases.

(DL)

143

(trivial error)

In the code display in the middle of the page, the line:

        my $q = $dbh->callbackquery(sub { my %F=@_; $F{STATE} eq 'NY'});

is missing a space:

        my $q = $dbh->callbackquery(sub { my %F=@_; $F{STATE} eq 'NY' });

144

(substantive error)

The code with the "punctuation that made my eyes glaze over" contains a minor error:

        @F{@{$self->{fields}}} = split $self->{FIELDSEP};

fields should be in capitals here:

        @F{@{$self->{FIELDS}}} = split $self->{FIELDSEP};

(RS)

147

(substantive error)

The last line of code on the page,

    return [$position, $_] if $is_interesting->(%F);

should probably be:

    return $_ if $is_interesting->(%F);

Moreover, it should not be in boldface.

(trivial error)

Of the ten boldface lines in callbackquery(), only the second, third and fourth (the ones with seek() and tell()) should be boldface.

(PW)

148

(trivial error)

In the first line of HTML log file data, there should not be a space between ~ and mjd.

(PW)

151

(trivial error)

There should be a paragraph break just before the sentence that begins "If $it were an iterator...".

(PW)

152

(minor error)

In the first code display, the line

        open my($fh), "|-", "tac", $file
          or return;

should be

        open my($fh), "-|", "tac", $file
          or return;

(JCZ)

(minor error)

At the bottom of the page, the line

    my $q = $db->callbackquery(sub {my %F=@_; $F{PAGE}=~ m{/book/$}});

should be

    my $q = $db->callbackquery(sub {my %F=@_; $F{page}=~ m{/book/$}});

because the keys in the %F hash must match the names passed in the call to new on the previous line.

(JCZ)

152-153

(substantive error)

The do block should be while (1), or perhaps a do-while of some sort.

(JM)

158-160

(minor error)

Each of the three pages has a call to imap that divides numbers by 37268. Each division should be by 32768 instead.

(IS)

161

(trivial error)

The dashed line that leads into the "return" box on the right is missing its arrowhead.

(PW)

163

(trivial error)

In the last paragraph of section 4.4.4, the phrase "the next argument it tried" should read "the next argument is tried".

164

(trivial error)

The sentence beginning "this won't work because..." is ungrammatical. It has the structure "... the caller will not be able to distinguish between ... from one that ...". It should read either "... the caller will not be able to distinguish between ... and one that ..." or "... the caller will not be able to distinguish ... from one that ..." as the reader prefers.

166

(substantive error)

The first paragraph in section 4.5.2 refers to "listrefs". There is no such thing. It should have said "arrayrefs".

(JW)

168

(substantive error)

The text mentions "the test for ref($arg) eq 'ARRAY'", but the code on page 167 that is being described actually has just ref($arg). I made a late change to simplify the code, and forgot to change the following discussion to match.

176

(minor error)

There is a close parenthesis missing at the end of the Iter::value() function.

(WL)

177

(not an error)

The title of section 4.6.1 is "Using foreach..." but then the next paragraphs discuss for. In Perl, for and foreach are identical, as I mention on page 451. Because they're interchangeable, I tend to interchange them without meaning to, sometimes with confusing results.

180

(trivial error)

There's a extra space in the my $stop_size line that misaligns the = sign.

(GC)

180-181

(trivial error)

Both versions of each_array() are missing a space before the : on the third line.

(not an error)

The version of each_array() on page 180 uses ref $arrays[0] to test the first argument; if it is a non-reference, it is taken to be the label of a stop-type function. In the version on page 181, this test has changed to UNIVERSAL::isa($arrays[0], 'ARRAY'). That's because the page 181 version is expecting that the first argument might be a code reference, so the simpler ref test will no longer work.

(GC)

(trivial error)

On each page, there is an extra space in the line:

        return ()  if $cur_elt >= $stop_size;

(JPL)

185

(substantive error)

In the middle of the page, the phrase "If you later ask the hash what's stored under that key" should be "If you later ask the scalar what it contains".

188

(substantive error)

The code

        if ($content_type eq 'text/html') { 

assumes that the Content-Type field will always have a bare content type. But actually, the HTTP/1.1 standard allows the field to contain zero or more "parameters", so that the $content_type variable might contain (for example)

        text/html; charset=ISO-8859-4

in which case the test above will fail even though it should have succeeded. The test here should be replaced with

        if ($content_type =~ m{^text/html\b}) {

and similarly on pages 192, 193, and 196.

(AR)

190

(substantive error)

The sample callback arguments contain relative URLs. This is the behavior if the $base argument to get_links() is undefined. However, Grasshopper doesn't use get_links() in that way; it always passes a URL in for $base. The result is that the URLs passed to the callback are absolute URLs, interpreted relative to $base.

(GC)

(trivial error)

There is a space missing before the => in 'SRC'=> and 'LOWSRC'=>.

191

(minor error)

print F, get($url) should be print F get($url).

(PW)

192

(minor error)

In the last code display, print F, $content should be print F $content.

(PW)

193

(substantive error)

$html is declared twice. The inner declaration

        my $html = get($url);

should be

        $html = get($url);

(This error also appears on page 196 in the first printing.)

(JCZ)

194

(minor error)

In the second line of code on the page, s/^\Q$top/o should be /^\Q$top/o.

(JQ)

195

(trivial error)

In the first code display, there should be a space between =~ and /^q/i.

196

(substantive error)

$html is declared twice. The inner declaration

        my $html = get($url);

should be

        $html = get($url);

(This error also appears on page 193 in the first and second printings.)

(AR)

201

(trivial error)

In the fourth line from the bottom of the page, WWW:SimpleRobot should be WWW::SimpleRobot.

(AL2)

215

(minor error)

There is an extraneous space between the # and the ! in the shebang line of the example.

(GC)

(trivial error)

The last line of code on the page is missing a space between the ) and the {.

216

(trivial error)

There's an extra space after the % sign in n % 2 == 0.

(JPL)

217

(trivial error)

The reference in the first paragraph to "Figure 5.7" should be to Figure 5.5 instead.

(trivial error)

The third sentence on the page is missing a word. It should say "...when the call returns, it moves back up to the parent."

(JCZ)

220

(substantive error)

The tree in figure 5.6 is missing a node. The leaf labeled (4,1,1,1) should have a second descendant, labeled (2,2,1,1,1), to the right of the one labeled (3,1,1,1,1).

(LSL)

(trivial error)

There's an extra space after my $max, causing the following = sign to be misaligned.

(GC)

221

(trivial error)

The code display named partition-iterator-2 should be named partition-it-2.

(PW)

222-223

(major error)

The output produced by the make_partition() functions is not as is shown on page 223; the iterators generated by the code traverse the search space in depth-first order, but the sample output on page 223 is in breadth-first order. To fix this, change

        my $item = pop @agenda;

to

        my $item = shift @agenda;

in each function. The discussion in the first paragraph on page 223 is confused. It says:

Because we return each partition immediately, after putting its children onto the agenda, old nodes are never preempted by new ones, regardless of whether we use pop or shift.

This is completely wrong. When pop is used, nodes are taken from the end of the agenda; the nodes so taken are the ones most recently added, so they certainly do pre-empt the nodes that were on the agenda before.

(DG)

224

(minor error)

In the (perhaps unlikely) event that Perl's built-in sort function tries to use the partitions() comparator to compare an element with itself, the comparator will return an undefined value, which is a fatal error. For more robustness, add return 0; after the for loop.

(JPL)

228

(trivial error)

The -- in the code display in the middle of the page is in the wrong font.

(JPL)

(trivial error)

The close brace in the display in the middle of the page is indented one space too far.

(JPL)

231-232

(trivial error)

The "A:", "B:" and "C:" labels on the displays should be in fixed-width font.

232

(substantive error)

"pop 48, 80 from stack" should read "pop 48, 20 from stack".

(PW)

(substantive error)

In the middle of the last paragraph, the phrase "rebinding the variables to the appropriate variables" should be "rebinding the variables to the appropriate values".

(trivial error)

Near the bottom of the page, "create a new call frame and active it" should be "create a new call frame and activate it".

234

(trivial error)

In footnote 5, "O'Reilly and associates" should read "O'Reilly and Associates".

(JS)

235

(not an error)

The first line in the powerset_recurse function reads

        my ( $set, $powerset, $keys, $values, $n, $i ) = @_;

but it should read

        my ( $set, $powerset, $keys, $values, $nmembers, $i ) = @_;

This wasn't originally my mistake; I took the code as it was written in Mastering Algorithms with Perl.

(LSL)

236

(trivial error)

The last line of the code on the page, $i++;, is indented one space too far.

237

(minor error)

The phrase "rewrite the while loop as a for loop" should probably be "rewrite the until loop as a for loop".

(VL)

260

(trivial error)

In the middle of the page,

        node($m, upfrom_list($m+1) );

should be

        node($m, upfrom_list($m+1));

261

(substantive error)

The definition of show() is suboptimal, because the last element of its output will be followed by an extraneous instance of $". This isn't normally noticeable, because the default value of $" is a space.

(MZ)

272

(trivial error)

The typography in the expression 24 · 320 · 5 is all wrong; it should be 24 · 320 · 5.

(GB)

(trivial error)

The spaces following the commas in "278, 942, 752, 080" should be omitted.

275

(trivial error)

In the code display, the last line, which is output of the program, should be in boldface.

278-279

(not an error)

The boxes labeled MERGE should probably say UNION instead, since what's being used here is actually the union() function of p.274, and the behavior is different from that of the merge() function of p.271 that was mentioned in the analogous diagram on p.272.

(JPL)

281

(substantive error)

The first line of the charclass() function reads my ($s, $class) = @_;, but it should be my $class = shift;.

(KH)

285

(substantive error)

$regex3->show(30) should have been show($regex3, 30).

(WL)

288

(minor error)

The call to match() in the last code display should be to matches().

(JQ)

(trivial error)

In the last line of the page, "It presently generates..." should be "At present, it generates...".

291

(minor error)

In the cutsort() function, I wish that instead of this:

        insert(@pending, head($s), $cmp);
        $s = tail($s);

I had written this:

        insert(@pending, drop($s), $cmp);

A similar kind of change is possible in display_failures() on page 433, although less obviously preferable.

(not an error)

It may appear that list_to_stream() is broken, because, for example:

        my $list = list_to_stream( 1 .. 10 );
        1 while defined drop($list);

fails; under strict 'refs' it says Can't use string ("10") as an ARRAY ref. But no, the function is working as designed and as documented on pages 291--292. The final argument of list_to_stream() is required to be the "final tail" of the stream that is constructed, and must be "another (possibly empty) stream or a promise"; in this example it is neither. The correct call would be:

        my $list = list_to_stream( 1 .. 10, undef );

where the final undef argument is an empty stream, as in the example on page 292.

(O)

293

(trivial error)

In the middle of the page, the sentence "Depending on the outcome of the comparison, it now know, a new element..." is garbled. It should read "Depending on the outcome of the comparison, the function now knows a new element..."

(PW)

294

(trivial error)

The last paragraph says that "The next function takes a filename, an open filehandle, and...", but it should say "The next function takes an open filehandle, a filename, and...".

(PW)

295

(not an error)

The line

        tail(iterate_function(sub { _next_record($fh, $filename, $devino) }));

is designed to work with the iterate_function() function that is defined on page 263; this version returns a stream. There is another, different iterate_function() function on page 134, that returns a chapter-4-style iterator object; that won't work here. I should have given the two functions different names, but sometimes these things slip through the editing process.

(HDP)

(substantive error)

In the function tai64n_to_unix_time(), the line

        return [undef, $rec] unless s/^\@([a-f0-9]{24})\s+//;

should read

        return [undef, $rec] unless $rec =~ s/^\@([a-f0-9]{24})\s+//;

(PW)

296-297

(trivial error)

In the digest_maillog() function, there should be a space between =~ and the following /.../ in each of the five constructions that reads $rec =~/.../.

(PW)

303

(not an error)

Using a fixed constant like 1e-12 in close_enough() is a bad idea; one should calculate error relative to the size of the arguments. I didn't mean to suggest that one should use this exact code; it was intended only as an example. A similar consideration holds for the value of $e on page 305.

(WL)

304

(trivial error)

At the end of the last paragraph, "synatax" should be "syntax".

(GC)

305

(not an error)

See page 303.

(WL)

308-310

(major error)

The discussion of the tortoise-and-hare algorithm suggests that it can detect repeating values in a sequence without the memory costs of maintaining a record of the already-seen values. In most contexts, this is true. However, in the context of infinite streams, the tortoise-and-hare algorithm loses this benefit. After n iterations, the tortoise is at the nth stream element and the hare is at the 2nth. Therefore, there are n stream elements held in memory that will not be garbage-collected until the tortoise passes them.

Since the memory cost is proportional to n anyway, the simple, obvious method is probably better:

        sub cut_loops {
          my ($stream, $seen) = @_;
          $seen = {} unless defined $seen;
          my $nextval = drop($stream);
          return if $seen->{$nextval}++ ;
          return node($nextval, promise { cut_loops($stream, $seen) });
        }

The tortoise-and-hare algorithm does make sense in the context of the iterators of chapters 4 and 5.

310

(minor error)

The name of the function should be cut_loops, not cut_loops2. Otherwise, the recursive call inside the function won't work properly.

(MB)

311

(trivial error)

In the very middle of the page, in the phrase "and the total is ..., or ... .", there is a superfluous close parenthesis just before the comma.

(GC)

312

(trivial error)

In the inline equation in the middle of the page, the variables P and pmt should be in italics.

316

(major error)

The definition of add2() is wrong. Empty streams in this context represent infinite sequences of zeroes. So add2() should be:

        sub add2 {
          my ($s, $t) = @_;
          return $s unless $t;
          return $t unless $s;
          node (head($s) + head($t),
             promise { add2(tail($s), tail($t)) } );
        }

(WL)

326

(minor error)

In the return line of promote_if_h1tag(), the quotation mark should be a pair of apostrophes ''.

(WL)

(trivial error)

There should be a blank line between the two function definitions in the first display.

326-328

(trivial error)

The 'keeper' and 'maybe' tags are lowercase, which is inconsistent with the usage established on pages 30-32. See the correction for pages 32-33 for more information.

328

(major error)

The call to walk_html() at the top of the page is wrong. It says

        walk_html($tree,
               sub { ['maybe', $_[0]] },
               promote_if('h1'),
               });

in which the braces aren't even balanced. It should have been:

        walk_html($tree,
               sub { ['maybe', $_[0]] },
               promote_if(sub { $_[0] eq 'h1' }),
              );

(PB)

329

(trivial error)

In the first code display, the longest line should end with )) }); instead of with ))}); .

342

(trivial error)

In the comment at the top of the page, "appropriate" is misspelled.

(DT)

344

(trivial error)

On the second line from the bottom of the page, in "List::Util::reduce", the word "reduce" should be in monospaced font.

(DT)

348

(substantive error)

In the first version of fold(), at the top of the page, the line:

        $fold->($f->($x, $first), @_)

should read:

        $fold->($f->($x, $first))->(@_)

(GM)

349

(substantive error)

In the _i_or() function, there should be a declaration

        my $rv;

just after return sub.

(GC)

351

(substantive error)

$dbh->query($filename, $value) should be $dbh->query($fieldname, $value).

(WL)

354

(substantive error)

sub { $F{OWES} > 100 } should be sub { my %F = @_; $F{OWES} > 100 } as on page 143. Also, the same error occurs on pages 357 and 448.

(WL)

357

(trivial error)

The use overload directive should have a space before each of the four instances of =>.

362

(substantive error)

In the brief code display in the middle of the page and in the following sentence, the pattern /-{3}\n|\+3\n/ should read qr/-{3}\n|\+{3}\n/.

(PW)

363

(trivial error)

Near the bottom of the page, "does and with a newline" should be "does end with a newline".

(RS)

364

(trivial error)

The second paragraph should end "...with the features currently in Perl."

366

(trivial error)

In the code display at the top of the page, there should be a space between the =~ and the /\G.../ in each of the eight =~/\G.../ constructions.

(PW)

369

(substantive error)

The push @tokens, grep $_ ne "", $sep, $tok, $i will produce a lot of "undefined value" warnings as written; it would probably be better as push @tokens, grep defined && $_ ne "", $sep, $tok, $i. (Also similarly on page 371.)

(WL)

370

(trivial error)

In the paragraph numbered "3", the sentence "This occurs when $i the most recent input..." should read "This occurs when $i, the most recent input...".

(PW)

373

(major error)

Because the INTEGER lexer is called before the IDENTIFIER lexer, it will incorrectly tokenize a string such as a2 = p; the INTEGER lexer will extract the 2 out of the identifier a2, resulting in a syntax error later on.

Probably the best way to fix this is to replace the INTEGER pattern qr/\d+/ with qr/\b\d+\b/.

The same error occurs in the lexers on pages 374, 392, and 424. A related error occurs on page 436.

(WL)

377

(trivial error)

At the bottom of the page, the phrase "one if its productions" should be "one of its productions".

(TJH)

381

(trivial error)

The final }; on the page should be moved one space to the right.

(PW)

386

(trivial error)

The -> in the doorbell grammar should have been a right arrow symbol (→).

388

(minor error)

In the version of alternate() at the top of the page, the lines:

        if (($v, $newinput) = $p1->($input)} { return ($v, $newinput) }
        if (($v, $newinput) = $p2->($input)} { return ($v, $newinput) }

should be:

        if (($v, $newinput) = $p1->($input)) { return ($v, $newinput) }
        if (($v, $newinput) = $p2->($input)) { return ($v, $newinput) }

390

(major error)

The call to star($separator, $element) should be star(concat($separator, $element)). (Or else we should modify star() to implicitly concatenate its arguments, if it has more than one.)

(WL)

(trivial error)

The first complete sentence on the page has a dangling pronoun. It says "But it will be less efficient than the previous version, because it will call alternate() and concatenate() each time it is called...". The word it here refers to the parser constructed by star(), not to star() itself, and so should read "But this will be less efficient than the previous version, because the constructed parser will call...".

407

(substantive error)

In the middle of the page, the line

        my ($plus_token, $term_value, RIGHT) = @_;

is missing the $ on RIGHT.

Also, $plus_token should be $minus_token.

(MK)

429

(trivial error)

In the code display at the bottom of the page, the apostrophes should be neutral single quotes.

436

(minor error)

my(..., $qatoms) should be my(..., $qatom).

(WL)

(substantive error)

The lexer, as written, doesn't correctly handle regexes that contain backaslashed metacharacters. This is because the PAREN, QUANT, and BAR lexers take precedence over the ATOM lexer, and get first crack at interpreting those characters, before the backslash-escape part of the ATOM lexer can see them. The correct lexer handles the backslashes with higher precedence, passing control to the metacharacter lexers only for unbackslashed characters:

  my $lexer = iterator_to_stream(
          make_lexer($input,
                  ['ATOM',       qr/\\x[0-9a-fA-F]{0,2} # hex escape
                                   |\\\d+               # octal escape
                                   |\\.                 # other \
                                   /x,              ],
                  ['PAREN',      qr/[()]/,          ],
                  ['QUANT',      qr/[*+?]/,         ],
                  ['BAR',        qr/[|]/,           ],
                  ['ATOM',       qr/./,             ]   # other char
                 )
   );

A related error occurs on page 373.

(WL)

436-437

(not an error)

Regarding the part of the lexer that handles hex escapes, which has

        \\x[0-9a-fA-F]{0,2}

Per Westerlund asks "I wonder if the intention was to allow hex escapes with 0, 1, or 2 following hex digits? What does \x mean in this context?"

That's a good question! It is intentional; I carefully checked the behavior of the regex engine while I was writing this section. In the construction \x0a, the leading zeroes are optional, and in fact they're so optional that you can write \x to mean \x00. I probably wouldn't want to use this feature in a real program, but in this case we might as well do what the real regex engine does.

(PW)

443

(minor error)

s/^ $BULLET//o should be s/^$BULLET//o.

(WL)

449

(trivial error)

Near the bottom of the page, the apostrophes in 'O'Reilly' should be neutral single quotes.

455

(minor error)

The code display in the middle of the page:

        my %string_version = ('>'=> 'gt', '>=', => 'ge', '==' => 'eq',
                           '<'=> 'lt', '<=', => 'le', '!=' => 'ne');

should look like this:

        my %string_version = ('>' => 'gt', '>=' => 'ge', '==' => 'eq',
                           '<' => 'lt', '<=' => 'le', '!=' => 'ne');

456

(trivial error)

The three -> symbols in the contrived example should be right arrow symbols (→).

466

(major error)

Using _ as the name of a subroutine is a bad idea. One reason is that the name _ is special, and is always forced into package main::. So for example:

        package Parser;

        sub _ { die "Ouch" }

        package Unrelated;

        $dir_readable = -d $dir && -r _ ;

does not do what you expect; you want _ to be interpreted as the name of a filehandle, but it is not. You have defined a subroutine named main::_ here, not Parser::_. Then, in the unrelated package, you think that in -r _ the _ is the name of a filehandle. Usually it is, and it usually names the special filehandle main::_. But now the Perl parser interprets it as a call to the function main::_(), and the statement dies.

Moreover, Wolfgang Laun points out that _ cannot be exported in the normal way, because of its special interaction with namespaces. For example, suppose you try to do:

        package F;
        use base 'Exporter';
        BEGIN { @EXPORT = '_' }

        sub _ { 
          print "_(@_)\n" 
        }

        package main;
        F->import();

        _(1..3);

Here we are trying to export _ from package F:: to package main::. But there is no _ in package F::; it is already in main::! So you get

        Subroutine main::_ redefined at /usr/local/lib/perl5/5.8.0/Exporter.pm line 59. at ...
        Undefined subroutine &F::_ called at ...

If you change the code to

        package F;
        use base 'Exporter';
        BEGIN { @EXPORT = '_' }

        sub F::_ {
          print "_(@_)\n" 
        }

        package main;
        F->import();

        _(1..3);

then it works as it should, for some value of "should". As far as I know HOP does not contain an instance of this error.

Wolfgang Laun points out other syntactic problems that are not exercised in HOP but that support the general conclusion that my attempt to use the compact notation

        _"...";

was a bad idea. The following expressions are syntax errors:

        _'arg'
        &_'arg'
        &_ 'arg'

although

        _ 'arg'

is acceptable. This is not actually a problem with _; it occurs because ' is still available as an obsolete synonym for ::. (This causes problems elsewhere; for example $x = "Fred"; print "$x's wallet" does not print "Fred's wallet".) On page 555 I used

        $base_name = _"IDENTIFIER";

which does work, but if I had used single quotes, it would have failed. Unlike the two problems described above, changing _ to __ will not fix this, because the problem is really with the ' symbol.

(GY)

467

(trivial error)

Near the top of the page, use overload '""'=> is missing a space; it should be use overload '""' =>.

474

(substantive error)

The - node at the lower-right in the diagram (connected to wires m, n, and p) should be a + node, since k = c + 273.15. The diagram on page 486 gets it right.

Similarly, the reference to the node in the middle of the page ("...causing the - node to set wire p to 310.15...") should describe it as a + node instead of as a - node.

(WM)

477

(substantive error)

The settor_is() function issues an inappropriate warning if it's called to check the settor of a node with no settor. The code has

        sub settor_is { $_[0]{S} == $_[1] }

but it would be better as:

        sub settor_is {
          my $settor = $_[0]{S};
          defined($settor) && $settor == $_[1];
        }

The function works properly without this change, except for the warning.

(VZ)

(substantive error)

There are errors in the set() function on page 476 and the revoke() function on page 477. The value() and has_value() methods each require an argument, to inform the methods of who is asking the question. set() and revoke() don't supply this argument, which is mandatory.

In set() on page 476, the lines:

        unless ($value == $self->value) {
          my $v = $self->value;

should be:

        unless ($value == $self->value($settor)) {
          my $v = $self->value($settor);

and similarly, in revoke() on page 477, the line:

        return unless $self->has_value;

should be removed entirely; if the request to revoke the value was made by the only component that is allowed to revoke it, namely the one that set the value in the first place, then has_value() will return false.

Without these changes, programs generate a lot of (inappropriate) "use of uninitialized value" warnings, and may produce incorrect results under some circumstances.

480-485

(trivial error)

Most of the code on these pages should be tagged as "Code library", downloadable from http://hop.perl.plover.com/Examples/Local_Propagation.pm. The example on page 485 should be tagged as downloadable from http://hop.perl.plover.com/Examples/local-propagation.

485

(substantive error)

The letter labels in the program code for the local propagation network are intended to match the ones in the diagram on page 483. But the labels $i and $j are switched, so the network that is constructed by the code is wired up wrong, and the network produces wrong answers. Instead of:

        { my ($i, $j, $k, $l, $m) = Wire->make(5);
          $F = new_io('Fahrenheit', $i);
          $C = new_io('Celsius', $m);
          new_constant(32, $j);
          new_constant(5/9, $l);
          new_adder($j,$k,$i);
          new_multiplier($k,$l,$m);
        }

one should have:

        { my ($i, $j, $k, $l, $m) = Wire->make(5);
          $F = new_io('Fahrenheit', $j);
          $C = new_io('Celsius', $m);
          new_constant(32, $i);
          ...

Alternatively, just change new_adder to new_subtractor on line 6.

(VZ)

492

(substantive error)

The display in the top half of the page of "four mathematically equivalent forms" contains one form that isn't equivalent. The four forms are

                  F + (hspc, 0) = plus;
                  plus + (-hspc, 0) = F;
                  plus - F = (-hspc, 0);
                  plus - (hspc, 0) - F = 0;

but the third one should be

                  plus - F = (hspc, 0);

instead.

493

(minor error)

In the display midpage, with examples of hidden constraints, one of the constraints is:

        top.start + wd = top.end

which does not make sense, since top.start is a point and wd is a scalar. It should have said:

        top.start + (wd, 0) = top.end

Similarly, the analogous constraint at the bottom of the page should be fixed analogously, to say:

        F.top.start + (F.wd, 0) = F.top.end

500

(trivial error)

In the last paragraph on the page, there is an extra comma after the phrase "The values 14, 9, and 3.5".

502

(trivial error)

In the first line on the page, the semicolon should be a colon.

502-512

(major error)

The Gaussian elimination technique for solving linear equations is notoriously unreliable in conjunction with floating-point numbers, and has a number of dangerous behaviors. I chose to use it anyway because it is a lot easier to understand than any other method I could have presented.

In this application, however, I have encountered at least one serious problem with the algorithm as presented in the book. Under some circumstances, the equation solver will fail to eliminate a variable from an equation when it should have. For example, suppose one equation is

        x + 2y + 2z            = 9

and another is

         2y + 2z            = 0

and the program uses the second equation to eliminate y from the first equation; the result should be:

        x                      = 9

But now suppose that because of an earlier floating-point roundoff error, the second equation is instead:

         2y + 1.9999999999z = 0

Then after eliminating y from the first equation, one has

        x +       .0000000001z = 9

If no other equations involve z, the erroneous z term will never be eliminated from this last equation, and the program will incorrectly conclude that it cannot determine the value for x.

For linogram, I think a suitable fix is to change the arithmetic() function on page 504:

        sub arithmetic {
          my ($a, $ac, $b, $bc) = @_;
          my %new;
          for my $k (keys(%$a), keys %$b) {
         my ($av) = $a->coefficient($k);
         my ($bv) = $b->coefficient($k);
         $new{$k} = $ac * $av + $bc * $bv;
          }
          $a->new(%new);
        }

We will add a special case that watches out for floating-point dust:

        my $EPSILON = 1e-6;

        sub arithmetic {
          my ($a, $ac, $b, $bc) = @_;
          my %new;
          for my $k (keys(%$a), keys %$b) {
         my ($av) = $a->coefficient($k);
         my ($bv) = $b->coefficient($k);
         my $new = $ac * $av + $bc * $bv;
         $new{$k} = abs($new) < $EPSILON ? 0 : $new;
          }
          $a->new(%new);
        }

If a new coefficient is calculated to be too small, we assume it is due to round-off error, and truncate it to zero. I think this is probably safe, because values in linogram nearly always represent distances in a diagram, and output devices don't have sufficient resolution to render meaningful differences on the order of $EPSILON. This change may, of course, introduce new problems. A safer alternative might be to use rational arithmetic throughout.

509

(trivial error)

Near the bottom of the page, the phrase "one if its variables" should read "one of its variables".

517

(trivial error)

In the reciprocal() function, the line

        my ($self, $coeff) = @_;

should be

        my $self = shift;

(BT)

519

(substantive error)

In the middle of the page, I said "we must use $self->component($k)->scale($coeff) rather than $self->component($k) * $coeff". The latter expression should have been $self->component($k)->value * $coeff.

(WL)

525-526

(trivial error)

The two displays on each page should be aligned.

530

(minor error)

The final "; is missing in die "Unknown tuple component ... 'kind'";.

(WL)

532

(trivial error)

The two instances of $_[0]{O}{$basename} would be better written as $self->{O}{$basename}, both for consistency and to avoid confusion between the 0 and the O.

(WL)

538

(not an error)

In add_constraints(), it might look as though this:

        push @{$self->{C}},
          $value->intrinsic->constraints,
          $value->synthetic->constraints;

should be this:

        push @{$self->{C}},
          $value->intrinsic_constraints,
          $value->synthetic_constraints;

but it's not so. The intrinsic_constraints() method is called on a Type object to calculate its basic set of intrinsic constraints from scratch, but the $value here is not a Type object but a Value::Feature object. intrinsic() is an accessor that returns the intrinsic constraint object already calculated for this expression, and constraints() extracts the list of constraints from this object. Similarly for the synthetic constraints.

(PW)

544

(trivial error)

The definition of $definition has

        $definition = labeledblock($defheader, $Declaration) ...

This will work, as long as $defheader is already properly defined, which it is. But for consistency, we should probably write this instead:

        $definition = labeledblock($Defheader, $Declaration) ...

(PW)

(trivial error)

In the middle of the page, the phrase "$extends is $line" should be "$extends is line".

(TJH)

545

(trivial error)

In the second paragraph, "Two others are..." should be "The two others are...".

(PW)

546-547

(trivial error)

The phrases (expression representing constant 3) and (expression representing variable 'boxwid') should be in a slanted monospace font.

551

(trivial error)

NAME => 'TOP' should read NAME => 'top'.

552

(substantive error)

The function add_subobj_declaration() should be named add_subfeature_declaration(), to match the entry in the dispatch table at the bottom of page 551.

(WL)

554

(substantive error)

The definition of %eval_op is missing. It should have at least

        %eval_op = ('+' => 1, '-' => 1, '*' => 1, '/' => 1,
                 'CON' => 1, 'VAR' => 1);

An earlier version of the program performed a constant-folding optimization, and used the following dispatch table:

        my %eval_op = ( '+' => sub { $_[0] + $_[1] },
                     '-' => sub { $_[0] - $_[1] },
                     '*' => sub { $_[0] * $_[1] },
                     '/' => sub { $_[0] / $_[1] },
                     'CON' => "special case",
                     'VAR' => "special case",
                   );

In the version of Linogram used in the book, it's probably best to eliminate this entirely, turning Expression::new into:

        sub new {
          my ($base, $op, @args) = @_;
          my $class = ref $base || $base;
          bless [ $op, @args ] => $class;
        }

(WL)

555

(substantive error)

In the definition of $name, the line > sub { ... } should read >> sub { ... }.

(WL)

557

(trivial error)

The two lines that read

        age = 4;

should read

        constraints { age = 4; }

instead.

559

(trivial error)

In the last paragraph, "named" should be "names".

573

(trivial error)

The line under "Unix" that mentions "epoch time format" should be indented.

575

(trivial error)

The function index entry for alternate2 is improperly run in to the preceding entry.


Thanks

AKAndrés Kievsky HDPHans Dieter Pearcey MKMatt Kraai
ALAndy Lester ISImre Saling MZMike Zraly
AL2Arjen Laarhoven JCZJean-Christophe Zeus O Ovid
ANON Anonymous contributors JKJim Kovacs PBPete Bevin
ARAxel Rose JMJeff Mitchell PWPer Westerlund
BTBennett Todd JPLJohn P. Linderman RJBSRicardo Signes
DFCDaniel Frederick Crisman JQJerome Quelin RSRon Savage
DGDavid Golden JSJesse Sheidlower SBSean Burke
DLDami Laurent JWJuerd Waalboer THThomas Herchenroeder
DTDennis Taylor KHKurt Hutchinson TJHJiahai Teng
FFFelix Finch KSKripa Sundar VLVincent Lee
GBGreg Bacon KWKen Williams VZVyacheslav Zakovyrya
GCGustavo Chaves LELeif Eriksen WLWolfgang Laun
GMGreg Matheson LSLLuc St-Louis WMWalt Mankowski
GYGaal Yahas MBMartin Busik   

Return to: Universe of Discourse main page | What's new page | Perl Paraphernalia | Higher-Order Perl

mjd-perl-hop+@plover.com