Purpose: Catch & throw etc., symbols, and more about functions
9.1 Catch and throw
We finished last time with blocks and the special operator return-from, which together allow you to make lexical exits, i.e. to leave any body of code so long as you are still in its textual region. So, for example, anywhere within the text of a function you can leave it by invoking return-from:
(defun safe-average (numbers)If you are no longer within the textual extent of a block, you cannot return-from it. The following:
(if numbers
(let* ((total 0))
(dolist (number numbers)
(if (numberp number)
(incf total number)
(return-from safe-average
(format nil "~a not a number" number))))
(float (/ total (length numbers))))))
(defun foo ()will not work because you you have to place the (return-from foo) lexically (i.e. textually) within the body of the function foo.
(bar))(defun bar ()
(return-from foo))
In contrast, the special operators catch and throw have dynamic scope and avoid the above restriction. (So why ever bother to use return-from? Answer: because return-from compiles into a simple jump instruction but catch and throw have to mess with the stack, which costs at run-time.)
Simple example to demonstrate the syntax:
CL-USER 10 > (defun basic-error (string)
(format t "~&; Error: ~a" string)
(throw 'restart-top-loop nil))
BASIC-ERROR
CL-USER 11 > (basic-top-loop)
>> (defun fact (x)
(if (and (numberp x) (>= x 0))
(if (= x 0)
1
(*
x (fact (1- x))))
(basic-error (format nil "~a
not a number >= 0!" x))))
FACT
>> (fact 4)
24
>> (fact t)
; Error: T not a number >= 0!
; Restarting top-loop.
>> :quit
NIL
CL-USER 12 >
With all these cute possibilities for getting out of executing vast chunks of code by invoking some exit (whether return-from or throw or aborting from an error), there is the chance that some form that simply had to be executed will be missed out. The classic example is that that once you've opened some external resource (for instance a file or database connection), you want to guarantee closing it cleanly no matter what else happens, i.e. even in the face of unexpected errors.
Lisp allows you to do this, and the special operator which handles it
is called unwind-protect. Its syntax is
(unwind-protect protected-form cleanup-1 cleanup-2
... cleanup-n)
unwind-protect evaluates protected-form and guarantees that the cleanup-forms will be executed before unwind-protect exits, whether it terminates normally or is aborted by a control transfer of some kind (i.e. return-from, throw, error).
(defun dummy (x)Notes:
(setf status 'running)
(if (numberp x)
(setf status (1+ x))
(throw 'abort 'not-a-number))) => dummy(catch 'abort (dummy 1)) => 2
status => 2(catch 'abort (dummy 'trash)) => not-a-number
status => running(catch 'abort (unwind-protect (dummy 'trash)
(setf status 'aborted))) => not-a-number
status => aborted
The function funcall takes a function and some arguments, and calls that function with those arguments. For example,
9.4 Closures
Another really neat feature of lisp is the ability of functions to access variables which are (lexically) bound around the function definition. We can retain the value of a variable from one function invocation to the next, and share a local variable between several functions, thus:
(let* ((counter 0))except that the above is not a very interesting example (because we could have achieved the same thing by making counter a global variable). However, note that the variable counter is shared between the three functions and that its value persists between function calls.
(defun add-one ()
(incf counter))
(defun query ()
counter)
(defun reset ()
(setf counter 0)))(query) => 0
(add-one) => 1
(query) => 1
(dotimes (i 5) (add-one)) => nil
(query) => 6
(reset) => 0
(query) => 0
Now consider the following (I think this is the first example we've seen of a function which generates another function as its return value):
(defun make-thingy ()Every time we call make-thingy, we bind a new variable thingy. Therefore the two functions stored in one and two work with their own private copies of this variable.
(let* ((thing nil))
(lambda (&optional new)
(let* ((old thing))
(if new
(setf thing new))
old))))(setf one (make-thingy)) => <some function object, prints ugly>
(setf two (make-thingy)) => <some other function object, prints ugly>(funcall one 'first) => first
(funcall two 'second) => second(funcall one) => first
(funcall two) => second
Finally, in
(defun extract (table)the variable values though external to the lambda form is lexically bound around it and so can be used by the lambda form to return a list of the values found in the hash-table.
(let* ((keys nil)
(values nil))
(maphash (lambda (key value)
(push key keys)
(push value values))
table)
(list keys values)))
The above functions (that is: add-one query reset, the functions returned by calls to make-thingy and the lambda form in extract) are examples of lexical closures. We say that these functions close over the variables counter, thing, keys and values, respectively.
Closures allow you to attach state to a function. (Maybe it's more precise to say that closures are functions with permanent state, possibly shared.) In the first example three closures share access to the same closure variable, in the second a new closure is generated each time make-thingy is called and the state is not shared, in the third case the state consists of two variables, both shared with the enclosing function.
This last case is the most common. Most (but by no means all!) closures are not seen as return values.
Without closures, lambda forms wouldn't be nearly so useful. The fact that closures exist means that you can write code like extract and never have to worry about how the lambda form is going to communicate with the function it belongs to. Still, Graham is right (page 2) when he notes that a function like
;; generate a function which adds n to its argumentsimply couldn't be contemplated in C.
(defun addn (n)
(lambda (x)
(+ x n)))
9.5 Symbols and names
We have on our travels encountered a number of general (i.e.
they can contain any lisp objects) data structures, as in the table in
last week's notes:
cons vector
structure hash-table
and a one specialized data structure:
string
We will look now at one semi-specialized structure which we've
been using all along: the symbol. I say semi-specialized because
one of its fields can contain what you like, and the others can't.
A symbol can be thought of as a named object. It is of type symbol, and responds positively to symbolp. Note that all keywords are symbols.
To access a symbol, type its name. To create a symbol, type its name (although see also the functions make-symbol and intern). To discover whether a symbol with a given name already exists, use the function find-symbol (which takes the name of the putative symbol as a string). To go from a symbol to its name, use the function symbol-name
(find-symbol "FOO") => nil ; since this symbol does not exist yet[Note about case sensitivity: the lisp reader by default converts all symbol names to upper case as they are typed, so that internally symbol names normally contain only uppercase characters. The lisp printer by default prints these names in upper case. You can mess with both sets of behaviour if you care to, though heaven only knows why you would. Look up topics like readtables (that's another specialized data structure), *print-case* and section 22.1.3.3.2 of the Hyperspec, if you care.]
'foo => foo ; simply typing the symbol creates it
(find-symbol "FOO") => foo ; and so the symbol is now present
(symbol-name 'foo) => "FOO"
(symbol-name :foo) => "FOO" ; symbols foo and :foo have the same name!
(type-of 'foo) => symbol
(mapcar 'symbolp '(foo t nil :wibble)) => (t t t t)
9.6 Symbols and values
A symbol may or may not have a value associated with it. If you know the name of the symbol when you're typing, then "evaluating the symbol" will return the symbol's value.
(setf foo 99) => 99 ; setf always returns the [last] new-valueIf the name of the symbol is only available at run-time, you can still access its value by calling the setfable function symbol-value:
foo => 99 ; current value of the symbol foo
(1+ foo) => 100 ; etc
foo => 99 ; do not confuse 1+ with incf
(symbol-value 'foo) => 99To discover whether a symbol does or doesn't have a current value definition (whether the symbol is bound), use the function boundp. To remove a symbol's value definition, call makunbound.(defun find-numb (symbols)
(find-if (lambda (sym) (numberp (symbol-value sym)))
symbols)) => find-numb(find-numb '(foo bar baz)) => foo
(setf (symbol-value (find-numb '(foo bar baz))) "hello") => "hello"
foo => "hello"
(boundp 'foo) => t ; the symbol foo currently has a valueIf you attempt to get your hand on the value of a symbol while it is unbound, you get an error. Any [non-keyword - why?] symbol you've just created from scratch is initially unbound.
(makunbound 'foo) ; we remove that value...
(boundp 'foo) => nil ; ... and it's gone(defun values-if-bound (symbols)
(let* ((values nil))
(dolist (sym symbols)
(if (boundp sym)
(push (symbol-value sym) values)))
(reverse values)))
(setf bar 'yes wibble 'no wombat 'maybe)
(values-if-bound '(foo bar baz wibble wobble wombat)) => (yes no maybe)
something-new => Error: The variable SOMETHING-NEW is unbound....9.7 Symbols and functions
Similarly, a symbol may or may not have a function associated with it. "Calling a symbol" as a function will invoke the appropriate function, if one has been defined.
(defun foo () t) => foo ; defun returns the symbol defined by itBy analogy to symbol-value, boundp and makunbound, you can access the function associated with a symbol by calling symbol-function, ask whether it exists with fboundp, and remove it with fmakunbound.
(foo) => t ; nothing new so far, I hope
(foo) => (<true>
; prints ugly (in LWW anyway)
<function definition for foo> ; also prints ugly
foo
nil)
(foo) => Error: Undefined function FOO called ...
(setf (symbol-function 'foo) (lambda (x) (bar
(wibble x))))
or
;; foo will now have the same function definition
as +
(setf (symbol-function 'foo) '+)
9.8 Summary
Symbols, which we've been using heavily since the very beginning, are
in fact data structures containing a number of fields. We have covered
three of these here. We won't have time for packages or property lists
(plists).
|
|
|
|
symbol-name | A string | No | The print-name of the symbol, used to identify the symbol uniquely (within its package) |
symbol-value | Initially unbound. Can be any lisp object | Yes | The value associated with this symbol "as a variable" |
symbol-function | Initially unbound. Can be any function (named or anonymous, closure or not, etc.) | Yes | The function associated with this symbol |
symbol-package | An object of type package (or possibly nil) | No, but see intern, import, export, etc. | Packages form (somewhat) private namespaces. For example, the difference between foo and :foo is that they are in different packages |
symbol-plist | A list of the form (name1 value1 name2 value2 ...), initially nil. The names must be symbols, the values can be anything. | Yes, but more usually accessed via setfable function get | To allow you to hang additional values (properties) off a symbol |
9.9 Practical session / Suggested activity