Return-Path: Mailing-List: contact mjd-book-help@plover.com; run by ezmlm Delivered-To: mailing list mjd-book@plover.com Received: (qmail 15229 invoked by uid 119); 14 Jun 2000 04:26:49 -0000 Date: 14 Jun 2000 04:26:49 -0000 Message-ID: <20000614042649.15228.qmail@plover.com> From: mjd@plover.com To: mjd-book@plover.com Subject: Chapter 3 not finished, but big progress in my brain Hi, folks. It's been a while since I had an announcement. But I promised low traffic, and I want to be sure I deliver. I was going to wait until I had finished another chapter before announcing anything, but there have been a couple of reasonably interesting developments recently. As some of you may know, I'm giving a class at TPC called `Advanced Programming Techniques with Perl' or something like that. The title should sound vaguely familiar. I got to give the class last week on the Perl Whirl, where it was a big hit, and I'll do it once more at YAPC next week. The slides for the class are now on-line; URL appears at the end of this message. (Of course, I left all the most cool examples out of the class.) One delightful thing that happened while I was putting together the class was that I realized that all my book material is a lot more interconnected than I realized at first. I had thought that the chapters would be fairly disconnected, sort of a big bag of five or ten major techniques. But it turned out that each item was related to all the other items in ways that I hadn't appreciated at first. For example, I had planned to show currying, but while I was putting together the class, I realized that currying is what you get when you combine closures with callbacks. And I had planned to show generic higher-order functions, such as 'fold', which is a function that constructs functions that operate on lists, but I don't think it had occured to me to think of a function that accepts callbacks as a special case of a higher-order function, or vice versa. So making up the class first is going to result in a much better book. This knitting-together process is continuing. Today I had a really wonderful experience that makes me feel better than ever about the book. I was talking to Jeff Goff on IRC, and he asked about the partitioning problem. This problem is to take a bunch of balls, and to try to find all the ways of dividing them into groups. For ex- ample, if you have four balls, there are five ways to divide them up: 4 3+1 2+2 2+1+1 1+1+1+1 (Note that duplicates, like 1+2+1 or 1+3, are not present.) It's not too hard to come up with a recursive solution, but Jeff wanted an iterator-structured solution, which means that he wanted an object which supported a 'next' method which generates a different solution each time it is called. I thought about this a little this morning and couldn't see how to do it. But then I was thinking about it again over dinner and I had a really great flash of inspiration that tied together three big themes in the book. One of these themes is genericity; the idea that when you have an algorithm to do something, you should abstract out all the specific behavior that you can until you're left with a pure algorithm, and then you can let the caller put that behavior back in by passing a bunch of callbacks. The very first example in the book, which is the 'hanoi' function, is about this: We start with a function that solves the Towers of Hanoi problem and prints a solution to the screen, and then turn it into a function that solves the Towers of Hanoi problem and performs an arbitrary action at each step. A second theme is that recursion is suitable for processes involving recursive structures and recursive spaces. And I had planned to talk about tree search, but I don't think I realized until today just how pervasive tree search actually is. But almost any search problem can be characterized as a tree search of some sort, and almost any recursive function can be viewed as a sort of search. A third theme is the use of lazy iterators to represent the set of solutions to a searching or other generation problem. In the class I taught last week, I showed a function that generates a list of solutions to a problem, and then I show the solution with an iterator, which in many ways is better. The recursive version generates all the solutions before it returns, which takes a really long time and is wasteful of time and memory. With the iterator solution, you extract exactly as many solutions as you need and then throw away the iterator. But the iterator solution requires a lot more code, and it's not clear how to transform the straightforward recursive solution into an iterator without applying ingenuity, which is always in short supply. I ended up by saying that you really need continuations to do this easily, and Perl does not have continuations. But I was wrong; you do not need continuations. The bright idea I had today was that any recursive problem can be cast in the guise of a tree search, and it isn't difficult to write a higher-order function which accepts a few callbacks for generating the tree and for recognizing solutions, and which constructs a tree search iterator object that searches the tree recursively and returns a solution each time it finds one. I rushed home and implemented the idea right away, and to my amazement: 1. It was very, very easy to write the code; there was almost no thought involved at all because each part was so simple and so self-contained. 2. It worked perfectly on the first try. (I don't know about you, but this never happens to me.) I was especially amazed because just a few hours before, using conventional procedural techniques, I had been unable to see how to wolve the problem at all. With the PATH techniques, I got the answer right away. If was almost totally formulaic. So it was a good day for me and a good day for PATH. You can be sure that this super example is going to appear in the book somewhere, probably accompanied by a less mathematics-oriented example. Here's the code. I have a more heavily commented version, but I looked it over and I think the comments detract from the readability. (Of course, it might be more cryptic than I think because you haven't yet read the chapters where I explain the techniques I'm using.) ---------------------------------------------------------------- #!/usr/bin/perl -x # This function constructs a generic tree-search iterator. sub make_search { my ($start_state, $is_leaf, $is_solution, $children) = @_; my @queue = $start_state; return sub { for (;;) { return undef unless @queue; my $s = shift @queue; if ($is_leaf->($s)) { return $s if $is_solution->($s); } else { push @queue, $children->($s); } } }; } # This function uses the generic constructor to manufacture an # iterator to solve the partition problem. sub make_partitioner { my ($n) = @_; my $correct_total = sub { $_[0][0] == 0; }; my $children = sub { my ($r, $m, $p) = @{$_[0]}; my @res; for my $i ($m .. $r) { push @res, [$r-$i, $i, [@$p, $i]]; } @res; }; make_search([$n, 1, []], $correct_total, sub { 1 }, $children); } # Main program starts here if (@ARGV != 1) { print "Usage: $0 number\n"; exit 1; } # Construct iterator my $it = make_partitioner(shift()); # Generate and print all partitions of the input argument while (defined(my $s = $it->())) { my @p = @{$s->[2]}; local $" = ', '; print "{@p}\n"; } __END__ ---------------------------------------------------------------- I really couldn't believe how easy it was to put this together. The essential part of the code is the make_partitioner function, which is eleven lines long; it depends on make_search, which also only eleven lines long and which is a totally interchangeable part that I expect to use over and over again for a hundred different problems. I started the book with the idea that I was going to show new, powerful techniques that would make hard things easy, but I wasn't quite sure that it would work out. But I think this example is a huge success. I feel better about PATH than I ever have before. Thanks all for your interest. I will probably be in touch again next time something happens, probably later in the summer when chapter 3 is ready. [ Sorry, class slides are available only to mailing list subscribers. Send mail to mjd-book-subscribe@plover.com to subscribe. ]