mjhoy.com

Symmetry (and higher-order functions)

Repetition and iteration

Let’s say you want to call some block of code three times. Let’s say that block of code happens to rotate a rectangle. OK, well, here’s some code that runs if you click it:

rect.rotate(60);

Do it three times! (This is the real JavaScript, by the way, using the Raphael JavaScript vector graphics library.)

That’s the equivalent of

rect.rotate(60);
rect.rotate(60);
rect.rotate(60); 

except that we get to see a nice spinning effect. Now, this repetition of code — as it is, just hard-coded to repeat three times — could be captured in an iteration:

var i, n = 3;
for (i = 0; i < n; i++ ) {
  rect.rotate(60)
} 

This code effectively says: “Set i to 0 and n to 3. If i is less than n, rotate the rectangle, and increment i. Repeat the ‘if’.”

If we wanted to rotate four times, we'd just need to change n.

Slightly more complex iteration

Now we want a rotation that applies itself to a copy of the previously rotated rectangle. The magical function that does this (implementation to be described below) can be clicked here:

doSomethingMagical();

Again, let’s say three times is our goal. How would we implement this?

Hard-coded, it might look like this:

var rect1 = paper.rect(0, 0, 10, 10); // Make a rect
var rect2 = paper.rect(0, 0, 10, 10).rotate(60); // Make a rect and rotate 60
var rect3 = paper.rect(0, 0, 10, 10).rotate(120); // Make a rect and rotate 120 

Pretty ugly, and not very reusable. (What if we’d rather it be 30 degrees, not 40?) An iterative procedure would look like:

var i, n = 3,
    lastRect = paper.rect(0, 0, 10, 10),
    currentRect;

for (i = 0; i < n; i ++) {
  currentRect = lastRect.clone().rotate(60);
  lastRect = currentRect;
} 

To understand this, you need to know that clone makes a new object that has the attributes of its “master”, including rotation.

In English this iteration is something like (excluding the initial state of variables): “If i is less than n, make a clone of lastRect and rotate it by 60 degrees. Assign the variable lastRect to the new clone, increment i, and repeat.”

Note that the iteration works by updating the variable lastRect to point to whatever the last rectangle created was, so that, when it loops for the next time, it’s copying the new rectangle.

Higher-order functions

The idea of iteratively applying a transformation to a graphic — and by “iteratively” here I mean, as above, making copies as you go — isn’t really tied to whether that transformation is a rotation or a translation or whatever. Ideally, we could give a function (called “iterate” below) a base object, the number of times to apply an iteration, and the transformation function to run; e.g.

var rect = paper.rect(0, 0, 10, 10);
var fn = function (obj) { obj.translate(20, 0); };

iterate(rect, 3, fn); 

Which would give us:

JavaScript nicely lets us capture the transformation function as a variable (“fn” above) and pass it as an argument to another function. The fact that iterate accepts a function as an argument means it is a “higher-order” function: a function which either accepts a function as input or produces one as output.

So, what would the implementation of iterate look like?

var iterate = function(obj, n, fn) {
  if (!(n > 0)) {
    return;
  }
  newObj = obj.clone();
  fn(newObj);
  iterate(newObj, n - 1, fn);
};

It’s similar in many ways to the for loops above. In English: “Return if n is not greater than 0. Make a clone of obj, and call fn passing in the newly created clone as an argument. Then iterate with the new object, 1 less than n, and fn.”

Iterate calls itself repeatedly until n is 0, and each time it does it creates a new object, transforming it.

Symmetry functions

It’s possible, then, to describe symmetries of these vector objects with some ease in JavaScript, though certain kinds of operations are much more simply described as a procedure than others. Translating, like above, is easy. “Pivoting” objects around a point is more difficult: it requires tracking that point across function calls and determining its relation to the current object with some trig functions.

With a bit more effort, it’s possible to wrap the symmetries produced in a object that can, itself, iterate. For instance, let’s say we have the following peculiar shape, produced by the following code:

var r = paper.partOfAnElizabethanStrap();

Remember that r is the base Raphael object. Now we call iterate, but note this is a different iterate than the one above:

var r1 = r.iterate(1, function() {
  this.scale(-1, 1); // Reflect over y-axis
  this.translate(100, 0);
});

In this implementation of iterate I’m doing something special. It accepts a number of iterations to perform and a function as its arguments. The function is called in the context of the cloned objects, and so I apply the transformations to this. The result of iterate I put in a variable r1.

r1 is not the cloned object that’s been moved and reflected. It’s the group of both of those objects; the result of the iteration. And the iteration can itself be iterated, which lets us say:

r2 = r1.iterate(1, function() {
  this.rotateAround(180, 50, -45);
  this.translate(-50, 0);
});

This pivots and translates the symmetry group r1 as a whole. The “pivoting” is done by a method called rotateAround. The result, which is all four objects, is stored in r2. To finish:

r3 = r2.iterate(4, function() {
  this.translate(100, 0);
});

You can see my implementation of iterate at Github. (There’s also an implementation of rotateAround). Note that it’s very proof-of-concept and experimental now. There’s currently some bugs when using multiple calls to rotate and rotateAround over iterations. If only I knew my trig.