Lisp for C programmers

This is a simple introduction to Lisp if your previous experience is with C on the Arduino IDE. It explains how you would handle the most common C constructs in uLisp.

This originally appeared as a post on the uLisp forum.

Statements, operators, and functions

C

In C statements are terminated by a semi-colon:

count++;

C contains a mixture of operators and functions. Operators use infix notation, and functions are followed by their arguments in parentheses:

Serial.println(count * 2 + 1);

uLisp

In Lisp statements are enclosed in parentheses (a Lisp list):

(incf count)

Operators and functions have the same syntax; they are the first item in the list, and the arguments follow in Polish notation (also called prefix notation):

(print (+ (* count 2) 1))

Variables

C

In C you define global variables by giving them a type:

int count;
int max = 60;
const int led = 13;

You assign to a variable using =:

result = val * 2 + 1;

uLisp

In uLisp variables can contain any type of value, and you define them using defvar. Constants are defined in the same way:

(defvar count)
(defvar max 60)
(defvar led 13)

You assign to a variable using a function called setq:

(setq result (+ (* val 2) 1))

Functions

C

Here's a typical C function with three integer parameters, which returns an integer result:

int scale (int value, int factor, int offset) {
  return value * factor + offset;
}

uLisp

Here's the equivalent in uLisp. As before, you don't declare the type of the arguments or result:

(defun scale (value factor offset)
  (+ (* value factor) offset))

Every expression in Lisp returns a value so you don't need an explicit return statement.

Local variables

C

You can create variables local to a function in C to store intermediate values. So, for the previous example you could write:

int scale (int value, int factor, int offset) {
  int product = value * factor;
  int result = product + offset;
  return result;
}

uLisp

Here's the equivalent in uLisp. Local variables are defined using the let* statement:

(defun scale (value factor offset)
  (let* ((product (* value factor))
         (result (+ product offset)))
    result)

for loops

C

The most common way to do iteration in C is using the for loop. For example, if you've got six LEDs connected to pins 2 to 7 on an Arduino you can define them as outputs using:

for (int pin=2; pin<8; pin++) {
  pinMode(pin, OUTPUT);
}

and then blink them in turn using:

for (int pin=2; pin<8; pin++) {
  digitalWrite(pin, HIGH);
  delay(500);
  digitalWrite(pin, LOW);
  delay(500);
}

uLisp

The equivalent in Lisp is the dotimes loop, which executes the statements within the loop n times. A loop variable is set to each of the values 0 to n-1:

(dotimes (pin 6)
  (pinmode (+ pin 2) t))

(dotimes (pin 6)
  (digitalwrite (+ pin 2) nil)
  (delay 500)
  (digitalwrite (+ pin 2) t)
  (delay 500))

In uLisp the second parameter in the pinmode function is nil for INPUT and t for OUTPUT. Likewise the second parameter in the digitalwrite function is nil for LOW and t for HIGH

while loops

An alternative iteration construct in C is the while loop, which is typically used to wait until a condition is true.

For example, this loop waits until a pushbutton connected between input pin 8 and GND is pressed, taking the pin low.

C

First we define pin 8 as an input with a pullup, and pin 13 as an output:

pinMode(8, INPUT_PULLUP);
pinMode(13, OUTPUT);

Next we wait until it goes low, and then light up the LED on pin 13:

while (digitalRead(8) == HIGH) {
  delay(100);
}
digitalWrite(13, HIGH);

uLisp

The equivalent in uLisp is loop, which repeats all the statements in a block until a (return) is encountered.

First we set up the pins:

(pinmode 8 2)
(pinmode 13 t)

Then here's the loop:

(loop
  (unless (eq (digitalread 8) t) (return)))
(digitalwrite 13 t)

or, an equivalent shorter version:

(loop
  (unless (digitalread 8) (return)))
(digitalwrite 13 t)

if statement

C

Here's a typical example of an if statement in C. It tests an analogue input, and lights an LED if the value is greater than a specified target:

if (analogRead(A0) > target) {
  digitalWrite(13, HIGH);
} else {
  digitalWrite(13, LOW);
}

uLisp

Here's the equivalent in Lisp. The if construct is followed by three parameters: a test, the then clause, and the (optional) else clause:

(if (> (analogread 0) target)
    (digitalwrite 13 t)
  (digitalwrite 13 nil))

You can write this more succinctly as:

(digitalwrite
  13
  (if (> (analogread 0) target) t nil))

or even:

(digitalwrite
  13
  (> (analogread 0) target))

You can replace single branch __if__ or __if not__ forms by __when__ and __unless__ to  improve code readability or to take advantage of the fact that they form a block that can contain multiple forms.

if … else if statement

C

A more complicated use of the if statement is typically followed by one or more else if statements to check which of a series of cases is true. The following example reads an analogue input and sets one of four outputs high depending on the value of the input:

int a = analogRead(0);
if (a < 64) { digitalWrite(2, HIGH); }
else if (a < 128) { digitalWrite(3, HIGH); }
else if (a < 192) { digitalWrite(4, HIGH); }
else { digitalWrite(5, HIGH); }

uLisp

The most convenient way of doing this in Lisp is using the cond (conditional) construct. This is followed by a series of tests, followed by one or more statements that get executed if that test succeeds:

(setq a (analogread 0))
(cond
   ((< a 64) (digitalwrite 2 t))
   ((< a 128) (digitalwrite 3 t))
   ((< a 192) (digitalwrite 4 t))
   (t (digitalwrite 5 t))))

The last test is usually t, which will always succeed if none of the previous tests have succeeded.

switch statement

C

For choosing between a number of fixed alternatives the C switch statement is often used. It is followed by one or more case statements to check which of a series of cases is true:

int key = Serial.read();
switch (key) {
  case 'C':
    clearScreen();
    break;
  case 'I':
    invertScreen();
    break;
  case 'R': case 'F':
    rotateScreen();
    break;
  default :
    printf("Key not recognised" );
}

uLisp

The equivalent in Lisp is the case construct. This is followed by a test, and then a series of keys each followed by one or more statements that get executed if that key matches:

(setq key (read-byte))
(case key
  (#\C (clear-screen))
  (#\I (invert-screen))
  ((#\R #\F) (rotate-screen))
  (t (print "Key not recognised")))

The last key is usually t, which will always match if none of the previous keys have matched.