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

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)