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 (code-char (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.