&key
parameters.[149] And, as in macro parameter lists, any parameter can be replaced with a nested destructuring parameter list, which takes apart the list that would otherwise have been bound to the replaced parameter. The
(destructuring-bind (x y z) (list 1 2 3)
(list :x x :y y :z z)) ==> (:X 1 :Y 2 :Z 3)
(destructuring-bind (x y z) (list 1 (list 2 20) 3)
(list :x x :y y :z z)) ==> (:X 1 :Y (2 20) :Z 3)
(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
(list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 20 :Z 3)
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2 20) 3)
(list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 20 :Z 3)
(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
(list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 NIL :Z 3)
(destructuring-bind (&key x y z) (list :x 1 :y 2 :z 3)
(list :x x :y y :z z)) ==> (:X 1 :Y 2 :Z 3)
(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
(list :x x :y y :z z)) ==> (:X 3 :Y 2 :Z 1)
One kind of parameter you can use with DESTRUCTURING-BIND
and also in macro parameter lists, though I didn't mention it in Chapter 8, is a &whole
parameter. If specified, it must be the first parameter in a parameter list, and it's bound to the whole list form.[150] After a &whole
parameter, other parameters can appear as usual and will extract specific parts of the list just as they would if the &whole
parameter weren't there. An example of using &whole
with DESTRUCTURING-BIND
looks like this:
(destructuring-bind (&whole whole &key x y z) (list :z 1 :y 2 :x 3)
(list :x x :y y :z z :whole whole))
==> (:X 3 :Y 2 :Z 1 :WHOLE (:Z 1 :Y 2 :X 3))
You'll use a &whole parameter in one of the macros that's part of the HTML generation library you'll develop in Chapter 31. However, I have a few more topics to cover before you can get to that. After two chapters on the rather Lispy topic of cons cells, you can now turn to the more prosaic matter of how to deal with files and filenames.
14. Files and File I/O
Common Lisp provides a rich library of functionality for dealing with files. In this chapter I'll focus on a few basic file-related tasks: reading and writing files and listing files in the file system. For these basic tasks, Common Lisp's I/O facilities are similar to those in other languages. Common Lisp provides a stream abstraction for reading and writing data and an abstraction, called
The most basic file I/O task is to read the contents of a file. You obtain a stream from which you can read a file's contents with the OPEN
function. By default OPEN
returns a character-based input stream you can pass to a variety of functions that read one or more characters of text: READ-CHAR
reads a single character; READ-LINE
reads a line of text, returning it as a string with the end-of-line character(s) removed; and READ
reads a single s-expression, returning a Lisp object. When you're done with the stream, you can close it with the CLOSE
function.
The only required argument to OPEN
is the name of the file to read. As you'll see in the section 'Filenames,' Common Lisp provides a couple of ways to represent a filename, but the simplest is to use a string containing the name in the local file-naming syntax. So assuming that /some/file/name.txt
is a file, you can open it like this:
(open '/some/file/name.txt')
You can use the object returned as the first argument to any of the read functions. For instance, to print the first line of the file, you can combine OPEN
, READ- LINE
, and CLOSE
as follows:
(let ((in (open '/some/file/name.txt')))
(format t '~a~%' (read-line in))
(close in))
Of course, a number of things can go wrong while trying to open and read from a file. The file may not exist. Or you may unexpectedly hit the end of the file while reading. By default OPEN
and the READ-*
functions will signal an error in these situations. In Chapter 19, I'll discuss how to recover from such errors. For now, however, there's a lighter-weight solution: each of these functions accepts arguments that modify its behavior in these exceptional situations.
If you want to open a possibly nonexistent file without OPEN
signaling an error, you can use the keyword argument :if-does-not-exist
to specify a different behavior. The three possible values are :error
, the default; :create
, which tells it to go ahead and create the file and then proceed as if it had already existed; and NIL
, which tells it to return NIL
instead of a stream. Thus, you can change the previous example to deal with the possibility that the file may not exist.
(let ((in (open '/some/file/name.txt' :if-does-not-exist nil)))
(when in
(format t '~a~%' (read-line in))
(close in)))
The reading functions—READ-CHAR
, READ- LINE
, and READ
—all take an optional argument, which defaults to true, that specifies whether they should signal an error if they're called at the end of the file. If that argument is NIL
, they instead return the value of their third argument, which defaults to NIL
. Thus, you could print all the lines in a file like this:
(let ((in (open '/some/file/name.txt' :if-does-not-exist nil)))