Purpose: Structures, a word about macros, functions of functions, anonymous functions
7.1 Structures
And so on to user-defined structures. An example:
(defstruct pointThis form is equivalent to a struct declaration in C. It defines a new type, called point, with three slots (or fields if that's a word you're happier with) called x, y and z.
x
y
z)
The defstruct macro gives us all of the following:
Example:
CL-USER 53 > (defstruct pointFinally: it quite often happens (when you have structures containing structures containing ... etc.) that it all gets a bit much when it is printed out. We will touch on this briefly later in the course, but for now note that you can control how each type of structure prints. Just be aware that it is possible to control this, and that by convention the output is typically wrapped in #< >. The reason for this is that the lisp reader is programmed to signal an error whenever it sees #< and so you can guarantee that once you've abandoned the baroque but re-readable #s syntax, your output can never be inadvertently re-read into the image.
x
y
z)
POINTCL-USER 54 > (defun distance-from-origin (point)
(let* ((x (point-x point))
(y (point-y point))
(z (point-z point)))
(sqrt (+ (* x x) (* y y) (* z z)))))
DISTANCE-FROM-ORIGINCL-USER 55 > (defun reflect-in-y-axis (point)
(setf (point-y point)
(- (point-y point))))
REFLECT-IN-Y-AXISCL-USER 56 > (setf my-point (make-point :x 3 :y 4 :z 12))
#S(POINT X 3 Y 4 Z 12)CL-USER 57 > (type-of my-point)
POINTCL-USER 58 > (distance-from-origin my-point)
13.0CL-USER 59 > (reflect-in-y-axis my-point)
-4CL-USER 60 > my-point
#S(POINT X 3 Y -4 Z 12)CL-USER 61 >
CL-USER 10 > (make-point :z 2 :x 0 :y 1)
#<A point whose x,y,z co-ordinates are 0,1,2>CL-USER 11 > #<A point whose x,y,z co-ordinates are 0,1,2>
Error: (etc)
7.2 Defining macros / expanding macros
You may have noticed that a growing number of the macros we have met:
defunhave a something in common. They are known as defining macros (they define global functions, global parameters, constants, structure types, and so on) and by convention these macros - and no others - start with the letters "def".
defparameter
defconstant
defstruct
(and more to come)
You may also have noticed me repeatedly telling you that a macro is always shorthand for some other, typically longer and nastier, piece of code. If curiosity bites, you can see for yourself what a macro call is going to expand to by throwing it at the function macroexpand-1. An example:
(macroexpand-1 '(and foo bar))) => (if foo (and bar) nil)A slightly hairier example, in which we see that many macros expand into all sorts of mean nasty things that we never wanted to know about. Note the use of the function pprint which is like print except that it attempts (with varying degrees of success) to respect your screen width, indentation conventions, etc.
CL-USER 3 > (pprint (macroexpand-1 '(defun foo (x) x)))If you think that's ugly, try macroexpanding a defstruct form, such as (defstruct foo bar).(TOP-LEVEL-FORM FOO
(DSPEC:DEFUN-AUX 'FOO
#'(LAMBDA
(X)
(DECLARE (LAMBDA-NAME FOO))
(BLOCK FOO X))))CL-USER 4 >
System macros (i.e. macros, such as defun, which you didn't define yourself) are not obliged to expand into anything you can read or understand, or even into standard Common Lisp. The first of the above examples did expand into code which is readable, comprehensible and still Common Lisp; the second is part-way readable but goes "under the hood" with (e.g.) DSPEC:DEFUN-AUX which we know from nothing; and defstruct is always revolting.
How system macros expand will typically differ between implementations,
e.g. between Xanalys and Franz, or between Franz on the PC and Franz on
Sun workstations.
7.3 Functions as arguments to other functions
We saw in lecture 5 how you can pass the name of a function to mapcar (and to other functions), for instance:
CL-USER 11 > (mapc 'print '(foo bar baz))Functions which mess with other functions are very much part of the scenery in lisp. Let's take look at just a couple of these...FOO
BAR
BAZ
(FOO BAR BAZ)CL-USER 12 >
7.4 Sorting things
The function sort takes a sequence and sorts it. Into ascending numerical order? descending? alphabetical? or what? - Well that depends on you: sort takes a second argument, a predicate (which we remember is a function called to in order to obtain false or true, i.e. a nil or non-nil answer), and this predicate is used to determine the ordering. When sort has finished, each pair of adjacent elements in the result has to satisfy our predicate.
For instance, suppose we want to sort some list of numbers such as (14 40 16 8 35 33) into ascending order, i.e. into a sequence such that < is true of each pair of adjacent numbers. We already have (< 14 40) and so these two numbers are in the right order. But (< 40 16) is false, and so these two numbers need to be swapped around. And so on...
Example:
CL-USER 36 > (defun lottery-choices-brute-force ()The function random takes one argument - a positive integer or float - and returns a pseudo-random number, non-negative but less than the argument and of the same type - examples might be:
(let* ((fourty-nines '(49 49 49 49 49 49))
(randoms-0-to-48 (mapcar 'random fourty-nines))
(randoms-1-to-49 (mapcar '1+ randoms-0-to-48)))
(sort randoms-1-to-49 '<)))
LOTTERY-CHOICES-BRUTE-FORCECL-USER 37 > (lottery-choices-brute-force)
(8 14 16 33 35 40)CL-USER 38 >
The first call to mapcar above results in six random numbers, each in the range 0...48. The UK lottery only accepts numbers from 1...49, hence the second mapcar to adjusts the values. The call to sort takes our numbers and sorts them into ascending numerical order.
Another example - sorting words into alphabetical order:
CL-USER 51 > (pprint (sort (vector "This" "is" "what" "I" "typed" "to"[Note the use of string-lessp (case-insensitive) here. To get case-sensitive ordering, in which the capitalized words would come at the head of the result, we can use the predicate string<. Don't bother to learns all these case-insensitive variations - if you ever need to use them for real you can look them up.]
"generate" "a" "string" "which"
"read-line" "returned.")
'string-lessp))#("a" "generate" "I" "is" "read-line" "returned." "string" "This" "to"
"typed" "what" "which")CL-USER 52 >
One further example:
CL-USER 52 > (sortNote that in all these cases, sort has returned a sequence of the same type (list, vector or string) as the original.
(copy-seq
"This is what I typed to generate a string which read-line returned.")
'char<)
" -.ITaaaacdddeeeeeeeegghhhhiiiiilnnnnoprrrrrsssttttttuwwy"CL-USER 53 >
WARNING! sort is a destructive function and will almost certainly mangle the input sequence beyond recognition. Never ever sort a program literal (e.g. anything you quoted, or built with #() or with "..."). In each case above, I knew I had a freshly created sequence to play with, whether generated by mapcar, vector, or copy-seq (which will take any sequence and produce a fresh copy of it). Look what happens if you destructively modify a literal:
CL-USER 57 > (symbol-name 'copy-list)
"COPY-LIST"CL-USER 58 > (sort (symbol-name 'copy-list) 'char<)
"-CILOPSTY"CL-USER 59 > 'copy-list
COMMON-LISP::-CILOPSTYCL-USER 60 > ;; oops, probably time to quit this lisp session ;-(
7.5 Reducing a sequence
The function reduce takes (in the simplest case) a function and a sequence. It uses this function first to combine the first two elements of the sequence, then to combine the result with the third element, then to combine this latest result with the fourth element, and so on until the whole sequence has been processed.
(reduce '+ '(1 2 3 4 5 6 7)) => 28
This works by adding 1 to 2, adding 3 to the result, adding 4 to that, etc.
CL-USER 8 > (defun combine-2-strings (string1 string2)7.6 Throw-away functions
(format nil "~a ~a" string1 string2))
COMBINE-2-STRINGSCL-USER 9 > (defun combine-strings (strings)
(reduce 'combine-2-strings strings))
COMBINE-STRINGSCL-USER 10 > (combine-strings #("This" "is" "what" "I" "typed" "to"
"generate" "a" "string" "which"
"read-line" "returned."))
"This is what I typed to generate a string which read-line returned."CL-USER 11 >
In the last example, I wrote a two line function (combine-2-strings) whose sole purpose was to be passed to reduce. Now, it often happens that you write a quickie like this, which will only be called from one place and which belongs to it to such an extent that you would rather not see them separately.
(defun combine-strings (strings)Please do not panic.
(reduce (lambda (string1 string2)
(format nil "~a ~a" string1 string2))
strings))
(defun lottery-choices-lambda ()... although I have to admit that all these mapcars look a little out of place here and quite frankly
(let* ((fourty-nines '(49 49 49 49 49 49))
(randoms (mapcar (lambda (x)
(1+ (random x)))
fourty-nines)))
(sort randoms '<)))
(defun lottery-choices-dotimes ()would have been cleaner.
(let* ((randoms nil))
(dotimes (i 6)
(push (1+ (random 49)) randoms))
(sort randoms '<)))
7.7 Hash-quote
In the literature (and in many people's code) you will find lambda forms prefixed by the syntax #', for example:
(mapcar #'(lambda (x) (1+ (random x))) fourty-nines)
instead of
(mapcar (lambda (x) (1+ (random x))) fourty-nines)
The simplest suggestion I can make [rather than pinning your ears back and making you listen to the full explanation] is that these two forms are identical.
You will also sometimes see #' before a symbol, denoting that the associated function is wanted, typically when passing an argument to mapcar, reduce etc. In these cases, you can [for the purposes of this course] replace the #' with an ordinary quote and everything will be fine. For example:
(reduce 'combine-2-strings strings)
and
(reduce #'combine-2-strings strings)
are essentially identical.
#' always denotes a function.
7.8 Practical session / Suggested activity
Suppose we have structure definition thus:
7.9 Further reading & exercises