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)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...
(format t "~&Value supplied was ~a." x)
(let* ((y (list x)))
(format t "~&Bung ~a in a list and you get ~a." x y)))
TEST-ITCL-USER 12 > (test-it 'foo)
Value supplied was FOO.
Bung FOO in a list and you get (FOO).
NILCL-USER 13 >
CL-USER 15 > (defun look-at-setf (thing)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:
(format t "~&Value supplied was ~a" thing)
(setf thing 99)
(format t "~&Value has been changed to ~a" thing)
thing)
LOOK-AT-SETFCL-USER 16 > (look-at-setf 'foo)
Value supplied was FOO
Value has been changed to 99
99CL-USER 17 >
(defun smallest-of-three (first second third)4.2 The danger of setf
(let* ((smallest first))
(if (< second smallest)
(setf smallest second))
(if (< third smallest)
(setf smallest third))
smallest))
Look carefully at the following interaction
CL-USER 17 > (defun look-again-at-setf (thing)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.
(format t "~&Value supplied was ~a" thing)
(setf thnig 99)
(format t "~&Value has been changed to ~a" thnig)
thing)
Warning: Syntactic warning for form (SETF THNIG 99):
THNIG assumed special.
LOOK-AGAIN-AT-SETFCL-USER 18 > (look-again-at-setf 'foo)
Value supplied was FOO
Value has been changed to 99
FOOCL-USER 19 > thnig
99CL-USER 20 >
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))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..
*MY-GLOBAL-VARIABLE*CL-USER 21 > *my-global-variable*
(99 KANGAROOS)CL-USER 22 > (setf *my-global-variable* '(100 elephants))
(100 ELEPHANTS)CL-USER 23 > *my-global-variable*
(100 ELEPHANTS)CL-USER 24 >
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))If you're lucky, the lecturer might get enthusiastic at this point and start illustrating what's going on here. (Remind him.)
=> (PENGUIN 1 VAN 19)
(cons 'horseless-carriage (cons 1 *survey*))
=> (HORSELESS-CARRIAGE 1 PENGUIN 1 VAN 19)
*survey* => (PENGUIN 1 VAN 19)
(setf *survey* (cons 'horseless-carriage (cons 1 *survey*)))
=> (HORSELESS-CARRIAGE 1 PENGUIN 1 VAN 19)
*survey* => (HORSELESS-CARRIAGE 1 PENGUIN 1 VAN 19)
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*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:
(list (list 'first-evaluated first-subform))
(list 'if
'first-evaluated
'first-evaluated
(list 'let*
(list (list 'second-evaluated second-subform))
(list 'if
'second-evaluated
'second-evaluated
third-subform))))
`(let* ((first-evaluated ,first-subform))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.
(if first-evaluated
first-evaluated
(let* ((second-evaluated ,second-subform))
(if second-evaluated
second-evaluated
,third-subform))))
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)
but
(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))
something-old)
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)Incidentally, related to push is the macro pushnew, which only pushes an object onto a list if it wasn't already there.
(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)
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)))))4.10 Practical session - recursive pratice
(decf (first (first (first buried))))
buried)
=>
(((-1)))(let* ((things (list 'this nil 'that)))
(push 'other (second things))
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.
(defun maximum (list)
(if list
(progn
(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*
*maximum*))
(defun in-better-maximum (previous-best list)
)