This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

This site uses cookies. Continue to use the site as normal if you are happy with this, or read more about cookies and how to manage them.

×

How a Lisp Course Helped My JavaScript

I recently spent a week watching and working through a MIT course from 1985 called Structure and Interpretation of Computer Programs. You may have heard of it. SICP is a course that teaches a Lisp dialect called Scheme. Why on Earth would I want to go on a 30 year old course in a language nobody uses? First of all, there's plenty of reasons to suppose that Lisp has a very good future, but also the important thing about SICP is not what it teaches you about a language, but how it makes you think about programs.

SICP teaches meta-linguistic programming. That is, it teaches you how to write programs by writing languages. I recently tried the meta-linguistic approach in JavaScript and I was surprised at how powerful it was. Will it work for you? I'll tell you my story and you can decide.

Table of Contents

WebGL animation

WebGL lets you create 3D images in a browser. You program it with JavaScript. Raw WebGL is pretty difficult to program, so most people use a framework like three.js to do a lot of the heavy lifting for them. A while back I did some WebGL coding and decided to try porting a little animation function I'd created to three.js. After an hour or so I'd ported the code, and this is how you call it:

THREE.Animatic.animateAttribute(earth.position, "x", 100, 5);

This is a tween function. It takes a JavaScript object – in this case the position of a 3D model of the Earth, and animates one of its attributes to some value. In the above code it's animating the x attribute to 100 over a period of 5 seconds. This is what it looks like:

Animating the x attribute to 100

The call is asynchronous, so it returns immediately while the animation runs in the background. This way of animating is very powerful because it can animate almost anything in JavaScript. You can change location, orientation, even colour. As an experiment, I decided to apply the principles from SICP to this little function. Was there some way I could create a small animation language based on it?

Abstraction

The book for the SICP course says that a meta-language should have these three features:

  • Abstraction
  • Combination
  • Closure

The first, abstraction, means identifying general-purpose concepts. It's important that they are general-purpose, because they can then be applied in several contexts and increase the power of the language. What general-purpose construct will I have in an animation language? The most obvious one is an animation. But what is an animation? Looking a the function I decide that the attribute name and the target-value were the animation. The object is irrelevant. So is the time it takes. If I slide the Earth sideways by 100 points in 3 seconds, that's the same animation as sliding a fish sideways by 100 points in 3 seconds. So I created a new function called animate which combined the attribute-name and target-value into a single construct, like this:

THREE.Animatic.animate(earth.position, {x: 100}, 5);

This is a trivial change. Behind the scenes, animate simply splits the name from the value and calls animateAttribute as before. But now that I an abstract construct that represents an animation, I can apply to several different objects:

var slideOver = {x: 100};
THREE.Animatic.animate(earth.position, slideOver, 5);
THREE.Animatic.animate(fish.position, slideOver, 15);
THREE.Animatic.animate(car.position, slideOver, 15);

OK, what's next?

Combination

The next feature of a meta-language is combination. Combination is some way of collecting abstractions together. Let's say we're going to run a couple of different animations:

THREE.Animatic.animate(earth.position, {x: 100}, 5);
THREE.Animatic.animate(earth.position, {z: 200}, 5);

The animate call is asynchronous – so these animations will run in parallel, and the above code will move the Earth diagonally, like this:The animate call is asynchronous – so these animations will run in parallel, and the above code will move the Earth diagonally, like this:

See example 3

Is there a way we can combine these two animations together? Yes, we can by simply adding them to the same JSON object:

THREE.Animatic.animate(earth.position, {x: 100, z: 200}, 5);

Again, this is a very simple change to make. Instead of reading a single key/value pair from the animation object, we just loop through all key/value pairs. The animation object is starting to feel more useful now. We can animate several attributes at once with a single line of code.

Closure

The third feature of a meta-language is closure. This doesn't mean the closures that you see in functional programs. Closure means that each part of an abstraction can itself be an abstraction. So in the animation language, each target value can be a number, or it might be… an animation. The easiest way to understand this is with an example. Let's say, instead of sliding the Earth sideways, we wanted to roll the Earth. We can use the animate call to change the Earth's location, as well as it's rotation:

THREE.Animatic.animate(earth.position, {x: 100, z: 200}, 5);
THREE.Animatic.animate(earth.rotation, {z: -100 / 60, x: 200 / 60}, 5);

The first line animates the position, the second animates the rotation. But they are both animating the Earth. So rather than have two calls, why not have one?

THREE.Animatic.animate(earth,
     {position: {x: 100, z: 50},
      rotation, {z: -100 / 60, x: 50 / 60}},
     5);

The target value that we're changing the Earth's position to is not a number, but the animation {x: 100, z: 200}. It only takes a small amount of recursion to do this, but now we can create this animation with a single line of code:

Rolling the Earth

This small change has given us a lot more power. We can encapsulate a complex combination of different animations inside a single JSON object. That can help us, say, blow up the World:

THREE.Animatic.animate(earth,
    {scale: {x: 5, y: 5, z: 5}, material: {opacity: 0}},
    1);

See the Earth explode

More combination

We've used combination to combine animations in parallel. What about in series? Connecting aynchronous calls in series is difficult, because you need to wait for one activity to finish before starting the next. How about we pass a callback function that will be called once the animation is complete?

THREE.Animatic.animate(earth,
     {position: {x: 100, z: 200},
      rotation: {z: -100 / 60, x: 200 / 60}},
     5,
     function() {
         alert("Animation finished!");
     });

Running code when the animation is complete

Under the covers the original animateAttribute function had to be modified to accept the callback function, but this still onbly took a handful of lines of code. Now that we can respond the end-of-animation event, we can start to chain animations together like this:

function tick() {
    THREE.Animatic.animate(earth, {position: {x: 100}, rotation: {z: -100 / 60}}, 1,
        tock);
}
function tock() {
    THREE.Animatic.animate(earth, {position: {x: -100}, rotation: {z: 100 / 60}}, 1,
        tick);
}
tick();

When the tick animation is complete it calls the tock function, and vice-versa. This is the result:

Animations calling each other in turn

This is an important step because it means we can start to create control structures like loops, which would otherwise be very, very difficult:

function rotate() {
    THREE.Animatic.animate(earth, {rotation: {y: earth.rotation.y + 1}}, 1,
        rotate);
}
rotate();

This code performs a 1-radian rotation in 1-second, then immediately repeats itself, which results in a continuous rotation:

See example 8

Literal chaining

Now that we now how to use callbacks to connect animations together, let's add it into the language by using arrays. Let's say we're going to animate a pair of hands. We'll begin with the fingers. Each finger will have three animated bones called the phalanxes. This will animate the finger to a rest position:

var rest = {
    phalanx1: {rotation: {z: 0.6}},
    phalanx2: {rotation: {z: 0.5}},
    phalanx3: {rotation: {z: 1}}
};

Each hand will have four fingers and a thumb. So we can put the entire hand at rest like this:

var handRest = {
    thumb: rest,
    finger0: rest,
    finger1: rest,
    finger2: rest,
    finger3: rest
};

This is using the existing power of our animation language. Now we'll add one more feature. We've create a handRest animation for a single gesture, now let's chain a series of similar gestures together using an array:

var handSequence = [handRest, handStraight, handGrip, handRest, handFist, handRest, handPoint, handStraight];

The code that reads the array needs to create a series of animations, each one chained to the next with a callback. Now when you call this animation:

THREE.Animatic.animate(hands,
  [
    {left: handSequence, right: handSequence}
  ],
  6);

You see something quite complex:

See hands animation

For more information about my extension to the three.js project, see my fork of it on GitHub.