The trick is to separate the image of the fish in two separate parts. The first part is the fish-painter. It knows how to draw a fish in any rectangle that you provide. In other words it is a procedure, which given a rectangle will draw a fish inside that rectangle. If you give it a small rectangle it will draw a small fish, if you give it a narrow rectangle it will draw a narrow fish inside it. The second part is the rectangle. The smart thing about separating the knowledge about how to draw the fish, and where to draw it, is that you can create an algebra of painters.
What is an algebra? An algebra is a set of objects and some operations on those objects, such that applying those operations on the objects will produce an objects that belong to the set. For instance the natural numbers are closed under the operations of addition and multiplication, because when you add or multiply two or more natural numbers you and up with a natural number. In this way you can indefinitely produce new natural numbers from old ones. In the same way we can combine the set of all painters with operations such as rotate90, flipVert, beside, and so on. For instance applying the operation beside on the two identical fish-painters will produce a new painter that knows how to draw the two fishes beside each other. By using simple operations together with recursion (of n steps) we can define a whole family of painters that know how to draw n fish-complexes that are related to each other, in similar way that Escher did. So his whole picture of fishes is only one painter!
If that is not mind boggling enough, how about abstracting one more step. We can abstract away the painters themselves and only keep the painter operations as our elements in a new set. We can now define new (meta)operations on that set, thus creating a new algebra. For instance look at the following meta operation:
squareOfFour(t1, t2, t3, t4) = below(beside(t3, t4), beside(t1, t2))
which given 4 operations will produce a new operation.When this operation operates on a given painter it will give a new painter as the result. This new painter will (when given a rectangle to draw into) produce an image.
For instance if id stands for identity operation then the call
squareOfFour[id, flipVert, id, flipVert]
will produce a painter operation that given a painter and a rectangle will produce an image with four images of the original painter, such that the original painter is drawn in the upper left corner, one flipped copy to the right of it, and copy of these two below them.
I hope you have the mind and taste for abstract reasoning, so that you see the beauty of this approach
.
P.S. This example was originally devised by Peter Henderson.