Declarative Languages
Lecture #4

Purpose: Mainly to do with lists...

4.1 Assigning values: the macro setf

We have seen two ways of creating variables. Both methods assign values to the variables at creation time. For example:

CL-USER 11 > (defun test-it (x)
               (format t "~&Value supplied was ~a." x)
               (let* ((y (list x)))
                 (format t "~&Bung ~a in a list and you get ~a." x y)))

CL-USER 12 > (test-it 'foo)
Value supplied was FOO.
Bung FOO in a list and you get (FOO).

CL-USER 13 >

What we haven't seen yet is how to change the value of a variable once it's been created. All will now be revealed...
CL-USER 15 > (defun look-at-setf (thing)
               (format t "~&Value supplied was ~a" thing)
               (setf thing 99)
               (format t "~&Value has been changed to ~a" thing)

CL-USER 16 > (look-at-setf 'foo)
Value supplied was FOO
Value has been changed to 99

CL-USER 17 >

The macro setf (at its simplest) takes a variable name and a new value. The variable is reset to the new value (and, incidentally, setf returns that value.) For example:
(defun smallest-of-three (first second third)
  (let* ((smallest first))
    (if (< second smallest)
        (setf smallest second))
    (if (< third smallest)
        (setf smallest third))
4.2 The danger of setf

Look carefully at the following interaction

CL-USER 17 >  (defun look-again-at-setf (thing)
                (format t "~&Value supplied was ~a" thing)
                (setf thnig 99)
                (format t "~&Value has been changed to ~a" thnig)
Warning: Syntactic warning for form (SETF THNIG 99):
   THNIG assumed special.

CL-USER 18 > (look-again-at-setf 'foo)
Value supplied was FOO
Value has been changed to 99

CL-USER 19 > thnig

CL-USER 20 >

Even though the function look-again-at-setf has a "typo" in the setf statement it appears to run happily (though spot the different return value). Heed the warning message! It's trying to tell you that your code is going to set a value into a symbol which was not one of your local variables - thnig is neither the name of a function argument nor bound in let*.  When we set thnig we are making a change which is globally and permanently visible: if a symbol has a global value then that value can be accessed by any function.

If you get the warning "FOO assumed special", it means either that you have a typo (as above) or that you are using and setting a variable which you have not bound in that function.

If you really mean to preserve values globally, please tell the lisp system that you intend to do so in advance.

CL-USER 20 > (defparameter *my-global-variable* '(99 kangaroos))

CL-USER 21 > *my-global-variable*

CL-USER 22 > (setf *my-global-variable* '(100 elephants))

CL-USER 23 > *my-global-variable*

CL-USER 24 >

This way you (a) suppress the warning message and (b) have to stop and think for 10 microseconds about why you wanted this value to be held globally..

Note by the way the lisp coding convention that global variables have a * at the beginning and end of their name. This makes them stand out in your code. If the screen starts looking cluttered with all the asterisks everywhere maybe you'll get the message and find another way of coding it.

4.3 Lists revisited

Very early on we met the function list, which allocates fresh lists from scratch:
    (list 1 2 3 4 5)  =>  (1 2 3 4 5)
We now introduce the function cons which takes two arguments, the second of which should (for novice lispers) be a list, and returns an extended list thus:
    (cons 'foo (list 1 2 3 4 5))  =>  (foo 1 2 3 4 5)
The first of the new list is the first argument to cons; the rest of this list is the second argument to cons.  In other words,
    (first (cons foo bar))  ==  foo
    (rest (cons foo bar))  ==  bar

You can keep extending lists (and make them arbitrarily long) with cons:
    (cons 'penguin (cons 1 '(van 19)))  => (penguin 1 van 19)
but note this very carefully: you have not changed the original list, nor have you changed the value of any variable (local or global) whose value was (which pointed to) that list:

(setf *survey* (list 'penguin 1 'van 19))
          =>  (PENGUIN 1 VAN 19)
(cons 'horseless-carriage (cons 1 *survey*))
*survey*  =>  (PENGUIN 1 VAN 19)
(setf *survey* (cons 'horseless-carriage (cons 1 *survey*)))
If you're lucky, the lecturer might get enthusiastic at this point and start illustrating what's going on here. (Remind him.)

4.4 The empty list and nil (again)

Recall that (a) nil (apart from standing for logical false) is identical to the empty list:
    ()  =>  nil
and (b) I invited you to use cons to extend lists (any lists). So enquiring minds will wonder what happens if you try to extend an empty list. We might ask: what does the following return?
    (cons 'foo nil)

4.5 Building yucky lists, and another spoonful of syntactic sugar

We now have perfectly useful ways of building and extending lists. Let's quote an example

(list 'let*
      (list (list 'first-evaluated first-subform))
      (list 'if
            (list 'let*
                  (list (list 'second-evaluated second-subform))
                  (list 'if
Revolting! (Because it looks ugly, it hides your meaning, it's error prone and it takes all night to type and debug it.) It's a shame we couldn't just quote the whole of the list structure we were trying to generate here. (Why not?) Never fear, help is at hand:
`(let* ((first-evaluated ,first-subform))
   (if first-evaluated
     (let* ((second-evaluated ,second-subform))
       (if second-evaluated
The syntax we have introduced here is the backquote character, typically on the top row of the keyboard, to the left of the number 1. Do not confuse it with the (forward) quote which on UK keyboards lives underneath the @ sign.

The way backquote works is that everything in the following form is protected from evaluation (as with quote) UNLESS it follows a comma, in which case that item is evaluated normally. Simpler example:
    (list 'foo bar 'wombat)
is exactly the same as
    `(foo ,bar wombat)
It's up to you how you want to type this stuff, but I suspect that for anything other than flat lists (i.e. structures which you can build with a single call to list or cons) you will find backquoting simpler.

To add to the fun, backquote also supports list splicing. For example:
    (let* ((foo '(1 2))) `(wibble ,foo wobble))  =>  (wibble (1 2) wobble)
    (let* ((foo '(1 2))) `(wibble ,@foo wobble))  =>  (wibble 1 2 wobble)
The comma-at syntax is dead handy.

4.6 I am obliged to tell you the following

There are two functions in lisp called car and cdr. These are 100% identical to first and rest and come to us from some very ancient past, when one of them stood for "Contents of Address part of the Register" and the other for "Contents of Decrement part of the Register".

The ugly part of the story is the way you can combine calls to car and cdr. For instance instead of writing
    (car (cdr foo))
you can use the function cadr (but I would prefer you to use second). You can stack cars and cdrs up to four deep, with function names which start with a c, have up to four as and ds, and end with an r.

Please don't be tempted to use functions like cddadr though, as your code will convey nothing other than your bloody-mindedness to the next reader. Only the following are in common use:
    car cdr cadr cddr caddr
and all but one of the above (which?) has a more readable name anyway.

Oh, and since both (first nil) and (rest nil) evaluate to nil,  so do (caadar nil) and all that lot.

4.7 Two macros - push and pop

We saw above that setf is used to reset the value of a variable back where you found it. You'll frequently find yourself using it to cons something onto the front of a list and store the result, like this:
    (setf my-list (cons something-new my-list))
This construct is used so often that there's a macro to save your fingers from wear and tear:
    (push something-new my-list)
These two forms are equivalent.

The opposite of push is pop. The form
    (pop my-list)
is equivalent to
    (let* ((something-old (car my-list)))
      (setf my-list (cdr my-list))
So to pop a location containing a list, you set the tail of the list back into that location and return the head of the original list.

Note that in both cases, the original list structure is unchanged. All that has altered is the value of the variable (location) that was pushed or popped. For example:

(setf words '(opposite of push))  =>  (opposite of push)
(setf same-words words)           =>  (opposite of push)
(push 'the words)                 =>  (the opposite of push)
words                             =>  (the opposite of push)
same-words                        =>  (opposite of push)
(pop same-words)                  =>  opposite
words                             =>  (the opposite of push)
same-words                        =>  (of push)
Incidentally, related to push is the macro pushnew, which only pushes an object onto a list if it wasn't already there.
    (pushnew 'push same-words)        =>  (of push)

4.8 Incf and decf

Another useful pair of macros is incf and decf, which can be used to add (or subtract) 1 to / from locations. So:
    (incf foo)  ==  (setf foo (+ foo 1))  == (setf foo (1+ foo))
(And note, in the above, the function 1+ which adds 1 to its argument.) Similarly:
    (decf foo)  ==  (setf foo (- foo 1))  == (setf foo (1- foo))

By the way, both incf and decf take an optional second argument, if you want to increment / decrement by some value other than 1, e.g. (incf foo 2.3)  ==  (setf foo (+ foo 2.3))

4.9 More about setf

I started talking about setf as setting values to variables, and I then drifted into talking about it setting values into locations. The truth is, there are all sorts of places you can setf a value.

For instance,
    (setf my-list (list 'foo 'bar 'baz))    =>  (foo bar baz)
    (setf (first my-list)) 'wombat)         =>  wombat
    my-list                                 => (wombat bar baz)

We say that first is setfable, or that it is a setfable accessor, or that (first anything) is a setfable location.

So far, the we have met the following accessors which are setfable:
    first second ... tenth car cdr ...  cddddr
We will meet several more in weeks to come.

Further examples:

(let* ((buried (list (list (list 0)))))
  (decf (first (first (first buried))))

(let* ((things (list 'this nil 'that)))
  (push 'other (second things))
(this (other) that)

Note that when you setf into a location you are destructively modifying its contents. You should always be wary of code that attempts to do this as it can and frequently will have horrible and unforeseen consequences.
4.10  Practical session - recursive pratice
  1. Write a lisp function which takes two arguments - a list and a number - and returns true if (and only if) the number is = to the first element of the list.
  2. Write a lisp function which takes two arguments - a list and a number - and returns true if (and only if) the number is = to any element of the list.
  3. Write a lisp function which takes two arguments - a list and a number - and if the number is = to the any element of the list then return the position of that element in the list. (So if the number matches the first element in the list return 0, if it matches the second return 1, and so on.)
4.11 Suggested activity / exercises etc
  1. Go through the above notes and answer all the rhetorical questions.
  2. Graham: chapter 2, sections 1-12 and 15-16; chapter 3 sections 1-6, 9, 12, 16. At this stage in your learning, treat eql and equal as the same thing (they aren't but don't worry), and similarly for let and let*.
  3. Graham: pp 29-30 questions 1-5, 6a, 6b, 8 (recursive versions only), 9b.
  4. Rewrite the following using backquote notation. How are you going to test your answer?

  5.     (list 'if
              (list 'if second-subform third-subform nil)
  6. Consider the following code to determine the maximum number in a list (of arbitrary length):
    1. (defparameter *maximum* nil)

      (defun maximum (list)
        (if list
              (if *maximum*
                  ;; it's set hence this is not the first iteration
                  (let* ((first-number (first list)))
                    (if (< *maximum* first-number)
                        (setf *maximum* first-number)))
                ;; first time around
                (setf *maximum* (first list)))
              (maximum (rest list)))
          ;; list exhausted so return *maximum*

    Why is this a poor style of coding? (Consider what happens the second time the function is called.) It's better to use a "helper" function to pass state around, thus:
      (defun better-maximum (list)
        (in-better-maximum (first list) (rest list)))

      (defun in-better-maximum (previous-best list)

    Implement in-better-maximum
  7. Given that lisp defines for you a function last which returns the final cons in a list (NB as opposed to the final element), how do you access that final element? Why do you think last was designed like this?
  8. Consider the following:
    1. (let* ((whatever (list 0)))
        (1+ (first whatever))
      (let* ((whatever (list 0)))
        (incf (first whatever))
    Explain why they produce different results.
If you get seriously stuck, look at the solutions but please have a good go at each problem first.
Nick Levine
last modified 2000-09-13
Copyright (C) Nick Levine 1999. All rights reserved.
$Id: // $