Introduction to Lisp for C programmers


#1

Here’s the first part of a simple introduction to learning 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 topic is a Wiki - feel free to edit it to add information or improve what I’ve written (just click the Edit icon at the bottom of the topic).

28th October 2018: I’ve added a bit about statements, operators, and functions.
29th October 2018: Explained the Lisp cond statement.

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))

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:

if (key == 'C') {
  clearScreen();
} else if (key == 'I') {
  invertScreen();
} else if (key == 'R') {
  rotateScreen();
} else {
  Serial.println("Key not recognised");
}

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:

(cond
  ((eq key #\C) (clear-screen))
  ((eq key #\I) (invert-screen))
  ((eq key #\R) (rotate-screen))
  (t (print "Key not recognised")))

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