Thinking in a Lispy way


#1

In an earlier post, Introduction to Lisp for C programmers, I wrote about how, if you’re familiar with C, you can program in uLisp using similar constructs, just making adjustments for the Lisp brackets and the differences in the names of the language elements.

However, to really take advantage of Lisp’s features you need to think in a slightly different, Lispy, way. Here I’ll introduce three aspects of Lisp that can really make it easier to express your ideas in a project, with examples that typically occur when programming for a microcontroller board:

  • Using lists to represent multiple values.
  • Writing anonymous functions.
  • Writing a function that returns a function.

I’ve kept the symbol names short so you can run these examples on any platform, even an Arduino Uno.

The C way of doing things

Here’s the beginning of a typical Arduino program; suppose we’ve got digital I/O pins 2, 4, and 7 connected to LEDs. Then to light up the middle LED we need to do:

pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(7, OUTPUT);
digitalWrite(2, LOW);
digitalWrite(4, HIGH);
digitalWrite(7, LOW);

The direct equivalent in uLisp is:

(pinmode 2 t)
(pinmode 4 t)
(pinmode 7 t)
(digitalwrite 2 0)
(digitalwrite 4 1)
(digitalwrite 7 0)

Using lists to represent multiple values

However, in Lisp we can eliminate a lot of this repetition by representing the LED I/O pins as a list:

(2 4 7)

uLisp includes several functions to handle lists; for example mapc performs a function on every element of one or two lists. So to define the three pins as outputs we could write:

(mapc pinmode '(2 4 7) '(t t t))

This calls pinmode with the first elements of (2 4 7) and (t t t), then the second elements of (2 4 7) and (t t t), and so on (until the shortest list runs out). Likewise, to set the outputs we can do:

(mapc digitalwrite '(2 4 7) '(0 1 0))

Writing anonymous functions

We could write a function out that takes a single argument, the pin number, and configures that pin as an output:

(defun out (pin) (pinmode pin t))

Then we could supply out to mapc to define the three pins as outputs more elegantly:

(mapc out '(2 4 7))

What we’ve just done is to define a function out just so we could use it as the argument to the mapping function, mapc.

Even better, Lisp allows you to define a function in situ, without having to define it earlier with defun. This is called an anonymous function. This is especially useful if you only want to use the function in one place. The construct to do this is called lambda; you write:

(mapc (lambda (pin) (pinmode pin t)) '(2 4 7))

The lambda specifies the same body of the function without having to define it earlier.

Writing a function that returns a function

Suppose the three pins 2, 4, and 7 are connected to an RGB LED, and we want to work with this without having to remember which pins it is connected to. We could write a function led which takes a list of the pins, pns, that connect to the LED, and returns a function of three arguments, again using lambda, to write the colour states to those pins:

(defun led (pns)
  (lambda (r g b)
    (mapc digitalwrite pns (list r g b))))

We can use it like this:

(defvar rgb (led '(2 4 7)))

This calls led which returns a function that will write to the RGB LED, and we assign that to the symbol rgb. To set the LED to magenta we can now write:

(rgb 1 0 1)

If we had a second RGB LED connected to pins 8, 12, and 13 we use another call to led to create a second function:

(defvar r2 (led '(8 12 13)))

We can then make the second LED cyan by writing:

(r2 0 1 1)

We can now use the functions rgb and r2 to control the LEDs without having to remember which pins the LEDs are connected to.

Summary

These are a few simple examples demonstrating how you can do things with Lisp that make it easier to create practical applications with less programming effort.