The Forbidden Fruit
This lecture is about all the stuff we have been hiding about Lisp in
an effort to make it seem purely functional. Well, the proverbial cat
needs to get out of the bag for your next programming assignment...
SETQ
SETQ is a macro which can set either lexically or dynamically
scoped variables. Note that you do NOT quote the name of the
variable being assigned.
(setq foobar (sqrt 16))
You may also set an arbitrary number of variables with one setq; they
are processed in order.
(setq x (somefunction 1) y (otherfunction 2) z 4)
SETQ returns as a value the last value assigned. This is
surprisingly useful for debugging... consider:
(some-function-with-a-bug (setq debug-value x))
set
SET alters the value (or creates a value, if one doesn't exist)
of dynamically scoped variables. Consider:
(setq foobar 8)
(defun f1 (foobar)
(set 'foobar 10)
(f2)
foobar)
(defun f2 ()
(format t "FOOBAR IS ~S ~%" foobar))
> (f1 20)
FOOBAR IS 10
20
Note that the return value of f1 is 20 (lexically scoped) but the
FORMAT statements prints the value 10.
Be aware that you must quote the name of the dynamic variable
being assigned with set. If you don't, you'll need to make sure
that the name of the variable you do want to set is in the first
variable to set. (See my database code for more on this). Keep in mind
that Lisp evaluates all of its arguments.
> (setq foobar 10)
10
> (setq frip 'foobar)
FOOBAR
> (set frip 20)
20
> foobar
20
Note that dynamic variables do not "override" lexically scoped
variables. Consider:
(defun x1 ()
(set 'foo 17)
(x2 10))
(defun x2 (foo)
(cons foo (mapcar #'(lambda (x) (+ x foo)) '(1 2 3))))
> (x1)
(10 11 12 13)
In general, in this course the only dynamic binding you should be
doing is global variables. See section 9.2 of Steele for more on
dynamic variables.
Generalized Variables and SETF
SETF is a generalized variable setting mechanism. In short, if
you can specify a storage location, SETF will set it. Consider
that the elements of a list are storage locations, no? So:
> (setq foobar '(a b c d))
(A B C D)
> (third foobar)
C
> (setf (third foobar) 'z)
Z
> foobar
(A B Z D)
> (car foobar)
A
> (setf (car foobar) 'x)
X
> foobar
(X B Z D)
In fact, just about anything in Lisp can be set with SETF. If
it can't you can write your own DEFSETF which explains how to
do it! Like both SET and SETQ, SETF will
return the last value assigned.
SETF will (of course) work on variables (both types) as
well. SETF is really born out of the idea that there are
"access functions" and "update functions." Access functions "get"
values and update functions "set" values. So, what SETF does is
look at your access function and pick the corresponding update
function to make the change. For example, CAR is an access
function and RPLACA is the corresponding update function.
Some of the older update functions are still left in Lisp for backward
compatability, but the newer data types don't have them.
LET
LET creates local variables, lexically scoped. These can be set
with SETQ. Note the lexical scoping:
(defun f3 (x)
(let (x)
(setq x 10)
x))
> (f3 20)
10
LET is a implicit PROGN by the way. You may put any
number of variables in the parenthesis of the let. These variables
should be considered unbound, although they will (in fact) be
bound to NIL. If you want to initialize a variable at the time
you create it (and it is good style to do this even if you want the
variable bound to NIL), you can substitue a list for the
variable name. This list must have two elements,the first is the
name of the variable, the second is a form to evaluate to initialize
it. Consider:
(defun f4 (x)
(let ( (x nil)
(y 7)
(z (sqrt 16))
)
...
))
These variables are assigned "in parallel" which means that you may
not reference locals being declared in this LET in the
initialization of other locals in this LET. This makes the
following an error:
(defun f4 (x)
(let ( (j nil)
(y (car j))
(z (sqrt 16)))
(format t "Z is ~S ~%" z)
))
> (f4 10)
>>Error: The symbol J has no global value
If you want this behavior, you can use LET* which does the
variable creation in the order specified.
Simple Loops
The simple looping constructs are DOLIST and
DOTIMES. These iterate over a list and the integers
respectively.
DOLIST
DOLIST iterates over each member of a list,
assigning that member to a local variable. Here is an example:
(dolist (i '( (a b) (c d) (e f)))
(format t "~S ~%" (car i)))
A
C
E
NIL
The NIL above is the return value of the DOLIST
construct. Note that as seen above, the DOLIST facility is only
useful for getting side-effects to take place (in general, this is
true of all looping facilities). If you want to return a value from
the DOLIST construct add a third parameter to the control
part of the DOLIST and it will be evaluated at the loop's end
and the value will be returned from the DOLIST. Note: This
really implies a local variable to do the calculation into... This is
starting to look a lot like PASCAL.
DOTIMES
DOTIMES is a looping facility that iterates over the
integers. It is formatted like the DOLIST structure (including
the optional return value) but the second part of the control list
must evaluate to an integer. Here we go:
(dotimes (i 5 37)
(format t "~S ~%" i))
0
1
2
3
4
37
Some important things here: The 37 is returned (this could have been
any Lisp form) , not the last value of the loop index variable. Also,
this iterates from 0 to 4, not 1 to 5 or 0 to 5. The reason? Look
up the definition of NTH and it will make sense.
For your Connect-4 assignment (which has nice, regular
properties about it) the DOTIMES facility is likely to do
very useful.
Holy Cow, The LOOP macro
- See chapter 26 of Steele for a full description!
I really find the LOOP macro quite frightening, and do not
attempt to use even 10% of its features. I stick to the stuff I
understand, LET, DOTIMES, etc. However, if you want an
idea of its power, consider this (from Steele):
(defun compute-top-value ()
10)
(defun unacceptable (x)
(equal x 6))
(defun square (x)
(* x x))
(loop for i from 1 to (compute-top-value)
while (not (unacceptable i))
collect (square i)
do (format t "Working on ~D now ~%" i)
when (evenp i)
do (format t "~D is a non-odd number ~%" i)
finally (format t "About to exit~%"))
Working on 1 now
Working on 2 now
2 is a non-odd number
Working on 3 now
Working on 4 now
4 is a non-odd number
Working on 5 now
About to exit
(1 4 9 16 25)
The above LOOP macro has six "clauses."
I find the most useful the WHILE and UNTIL keywords of
the LOOP macro. These let you write the normal while loops from
other languages.
If you want to bind variables in the LOOP macro, you can do
so with the WITH clause.
DEFVAR
This is the recommended way to declare a dynamic, global
variable. Convention dictates that the first character of the name be
an asterisk. Example:
(defvar *b-and-b-db* (list
(edge-construct 'beavis 'butthead 'friend)
(edge-construct 'butthead 'beavis 'friend)
(edge-construct 'butthead 'todd 'idol)
(edge-construct 'beavis 'todd 'idol)
(edge-construct 'butthead 'lolita 'date))
"Beavis And Butthead Database")
Note that the third parameter (optionally) to this macro is a
documentation string. The DEFVAR construct does not
require an initial value; you can just declare variables with
it.
DEFPARAMETER
A DEFPARAMETER is a DEFVAR which requires an initial value.
DEFCONSTANT
DEFCONSTANT is a DEFPARAMETER but asserts to the system
that this value may not change (allowing excellent opportunities for
compiler optimization). Any further binding to this name or
assignments of the value will result in an error. This is an excellent
debugging aid! You may use DEFCONSTANT more than once on the
same variable (on LUCID anyway).
Back To The CS2360 Home Page
Ian Smith (iansmith@cc.gatech.edu)
Last Modified 21 Feb 95 by Ian Smith (iansmith@cc.gatech.edu)