SICP 2.2.4 Solutions
September 29, 2014 15:16
If you don’t understand how the SICP picture library package is being used here, be sure to read the Exercises segment for this section first.
The exercise itself tells you exactly what to do: pattern this one on
right-split, but with
beside swapped. This works by recursively putting a ‘full-size’ painter below two half-size versions that
below will take care of. I find the syntax of having the first
below argument being the bottom one to be awkward (it follows math operators but not vaguely-acceptable English positional grammar). It does allow for this simple swap, which hints at the next exercise.
With the two procedures given as the arguments to
split, it’s easy to copy the pattern made clear in the last exercise. The output needs to be a procedure that takes a painter and a number as arguments, so the only other part to pay attention to that. In the solutions file I used an internal define, but a simple lambda would also work.
Using constructors and selectors brings to mind the system of working with rational numbers that we’ve already seen, and vector representations aren’t all that different. As the
ycor-vect are suggested selectors, it seems natural to take those two coordinates as the arguments for
make-vect (which was left unspecified) and form a pair.
Adding, subtraction, and scaling are hopefully familiar concepts to you, and the book’s formulas are simple enough to follow. Each output will be a new vector (created with
make-vect) that is computed using the corresponding x or y coordinate. The tests can’t really check for it, but if you’re properly making use of
ycor-vect, it should be possible to completely change the definitions of those two and
make-vect without it affecting the arithmetic operations.
In case it’s not clear, the required selectors are the ones made use of in
frame-coord-map, which means that our points (that is, the arguments to
make-frame) should probably be vectors. There’s no real requirement of that (as
make-frame is not used elsewhere) but it certainly simplifies the selectors. Those are required to return vectors, since
frame-coord-map uses the vector operations.
If we use vectors, then, the first form of make-frame simply involves pulling from a particular point in the list of three. The second form is a nested pair, but isn’t all that different. Proper application of the a’s and d’s in a
cxxr expression will get the desired vector. These are all one-line procedures.
Here it is implied by the problem statement, and the way in which
segments->painter is defined, that our selectors will yield vectors as well, for use by
draw-line. That is indeed how I defined
draw-line, and thus the segment operations are merely a matter of storing and retrieving the vectors.
In truth I kind of cheated with the constructor. It would be better for the sake of abstraction to actually pass the vectors, not two pairs of numbers, to make-segment. In this case I opted for the more concise construction expressions, in which only four numbers are required instead of making vectors every time. Partly I also think it odd that the two vectors for the segment are effectively used merely as points. If instead one specified the start with a vector, and then the second to specify the segment itself (as with frame edges), I think it would make more sense to construct segments directly from vectors.
Creating the basic shapes isn’t that hard to do — only a few segments are necessary. This is more of a test to exercise the segment operators as defined in the previous exercise. The ‘framebox’ painter did reveal what I considered a tiny flaw in the painter library: the image actually paints from 0 to 127 pixels, meaning that if a full-size frame is used, the edge of the box is drawn outside of the snip’s area. I handled this automatically when the frame box is drawn, and ignored it at other times. Close inspection of the diamond, for instance, will reveal that the tip is missing on the upper and right points.
The last image (the wave-painter) is merely the result of tracing the image by hand, using graphical tools to measure the length of each segment, and then scaling them appropriately.
These are all easily patterned on
flip-vertical, with the proper application of the unit vectors that describe the desired transformation.
In the first version, we pattern it on
beside and again adjust the points of our transformation. I prefer using
split-frac (a scalar value) to
split-point (a vector) so that it can be used in all points where it’s required.
The second version may well be shorter, but to my mind is harder to figure out just by looking at it. It’s a good comparison of concise code vs. readable code. This exercise also provides another chance to test the rotation functions, as any solution will require some combination of them.
These are free-form so there isn’t really a right or wrong approach to them. I added segments using a second painter (and combined them into another one) but that could just as well have been done by extending the list of points — in some sense this could be considered the ‘incorrect’ approach given that the exercise recommends modifications at the various levels of abstraction discussed. It’s a creative exercise, so it’s just as much about finding a level you’re comfortable with.
I’ve also provided an alternative set of solutions, showing off Racket’s Drawing Toolkit. (Full disclosure: this is how I originally solved the exercises, before finding the soegaard package). It is certainly a much more powerful library than the specialized SICP picture one. It’s suitable for drawing just about anything that is required, but requires more setup, and the output isn’t directly ‘readable’ as snips. Pictures are drawn in a different window on a particular canvas, which will have a drawing context. Other details such as pen color are provided, and a few examples are given in the file to show how shapes can be drawn directly.
In order to keep it simple, only one window and drawing context is used for all the pictures in the file. This allows
draw-line to not require any additional arguments. However, this also means any picture is immediately drawn over the one being shown. Therefore time delays are used, along with a function ‘slideshow’ to cycle through a few painters at a time.
This is just to show another way to implement the graphical details, and may not even be the best way to do it with these tools. More details on how it all works can be found by looking at the documentation for the Drawing Toolkit.