Testing a result

An important tool in writing programs is to be able to perform different actions depending on the result of a calculation.

First we need to introduce some functions for testing the value of an expression. Lisp includes a number of functions that give a true or false answer - they are called predicates, and often (but not always) have names ending with p.

In Lisp the convention is that nil, or the empty list, means false, and anything else means true. If you want to represent true you use the special Lisp operator, t.

Testing Lisp objects: eq

The most fundamental Lisp test is eq; it tests whether two Lisp objects are the same symbol or number, or point to the same list. For example:

> (eq 'cat 'cat)
t

> (eq 'cat 'dog)
nil

Testing numbers: <, <=, =, >=, >

The functions for testing numbers are the same as their mathematical symbol. For example = tests whether two or more numbers are equal:

> (= 2 2)
t

> (= 2 3)
nil

Likewise, > tests if the first number is greater than the second:

> (> 4 3)
t

Remember, Lisp uses prefix notation, so the function comes first.

The eq test can also be used for numbers, but it's more intuitive to use =.

Is something a number? numberp

You can test whether something is a number with numberp. So:

> (numberp 2)
t

> (numberp '(1 2 3))
nil

Is something a list? listp and consp

You can test whether something is a list with listp. For example:

> (listp '(1 2 3))
t

> (listp 1)
nil

Note that because nil is equivalent to the empty list it is strictly a list:

> (listp nil)
t

If you want to exclude the empty list use consp:

> (consp '(1 2 3))
t

> (consp nil)
nil

Testing for nil: null

The predicate function null returns t if the parameter is nil, and returns nil otherwise. Remember that nil evaluates to itself, so:

> (null nil)
t

> (null t)
nil

Conditional test: if

Now we're ready to use these procedures to perform different actions depending on the value of an expression. Lisp includes several different forms to do this, but the simplest is if which is like the if ... then ... else of other languages.

The form if takes three parameters.

  • The first parameter is evaluated.
  • If it returns a non-nil value, the second parameter is evaluated and returned.
  • Otherwise the third parameter is evaluated and returned. 

Here's an example:

(if (< (analogread 0) 512)
  (digitalwrite 2 t)
  (digitalwrite 3 t))

This will light an LED on pin 2 is the analogue input is below 512, otherwise light an LED on pin 3.

Here's how you would write max using if to find the maximum of two numbers:

(defun max (a b)
  (if (> a b)
      a
    b))

Combining tests: and, or, not

You can combine several tests using the procedures and, or, and not.

or can take any number of parameters, and returns t as soon as one of its parameters evaluates to t; otherwise it returns nil.

and can take any number of parameters, and returns nil as soon as one of its parameters evaluates to nil; otherwise it returns t.

not takes one parameters, and returns t if its parameter evaluates to nil; otherwise it returns nil.

For example, to play a beep if either of two buttons is pressed, assuming the buttons are wired to take the input high:

(defun bip ()
  (if (or (digitalin 4) (digitalin 5))
      (note 0 4))

This example omits the "otherwise" part.

Exercise

1. Test whether an object is a list of two numbers

Write a procedure n2 that returns t if its parameter is a list of two numbers like (2 3), and nil otherwise.

Check that:

(n2 '(45 67))

is t, and:

(n2 '(cat dog))

is nil.