The Department of Computer Science & Engineering |
STUART C. SHAPIRO: Lisp Short
Course
|
xemacs &
~shapiro/.xemacs/
, and, especially,
~shapiro/.xemacs/init-for-ac.el
.
.cl
C-h m
M-x run-acl
RET
C-h m
*scratch*
buffer is in Lisp Interaction modeC-j
after a Lisp form.
(exit)
or :ex
to the Lisp Listener.
nil
, an atom that evaluates to itself.t
, an atom that evaluates to itself,
and every other Lisp object except nil
.
and
and or
are Lisp macros
that take an arbitrary number of arguments, and do short-circuit
evaluations.t
,
nil
, or the value of the last expression evaluated under
their control, a theme in Lisp.
Exercise: Start a file for Lisp source code.
For now, just put some comments at the top saying what it's for.
;
Rest of line In line after code ;;
Entire line Indent like code ;;;
Entire line Start in column 1 #|...|#
Comment brackets Use for commenting-out code
defun
macro
(defun average (x y) "Returns the average of the numbers x and y." ;; Doesn't round or truncate integers (/ (+ x y) 2))
(type-of n)
for various numbers,
n
(load "file-name")
to Listener:ld file-name
to Listener:cl file-name
to ListenerC-c C-b
in source file bufferC-u C-c C-b
in source file bufferThen try:
(average
and then pause and look at the minibuffer
(average 5 7)
(average 5 8)
(float (average 5 8))
(discrim a b c)
to return the
square root of b2 - 4ac
(discrim 2 7 5)
should return 3.0
(floor 5.25)
and (round 5.25)
(defun +- (x d) "Returns x+d and x-d." (values (+ x d) (- x d)))
Try: (values)
discrim
, define (quad-roots a b
c)
to return the two roots of the quadradic equation
ax2 + bx + c = 0namely,
(-b + sqrt(b2 - 4ac))/2a
and (-b - sqrt(b2 - 4ac))/2a
(quad-roots 2 7 5)
should return -1.0
and -2.5
(if test-form then-form [else-form])
Note:
if
is a special formExample:
(defun fact (n) "Returns the factorial of n." (if (<= n 0) 1 (* n (fact (1- n)))))Exercise: Define
(fibonacci n)
to return the nth Fibonacci number: 1 1 2 3 5 8 13 ...
(trace
function-name ... function-name)
turns on tracing of the
named functions.(trace)
returns a list of the
functions being traced.(untrace
function-name ... function-name)
turns off tracing of
the named functions.(untrace)
turns off all tracing.
Typing C-c t
when your cursor is over a function name,
either in Common Lisp mode or in Inferior Common Lisp mode, toggles
tracing of that function.
ACL has a ACL trace facility with more controls.
Try tracing discrim
and quad-root
.
Try tracing fact
and/or fibonacci
.
Try these with the functions both interpreted and compiled.
Characters
Characters, like numbers, are "self-evaluating atoms". Their syntax is #\<name of character>. Try these:
#\a #\space #\newline
Lisp handles Unicode characters and character names.
#\latin_small_letter_a #\latin_small_letter_a_with_acute #\latin_small_letter_eth #\greek_capital_letter_sigma
Note that #\latin_small_letter_a is another example of Lisp choosing a print syntax.
Now try this with each of the characters above:
(format t "~a" #\latin_small_letter_a_with_acute)
Format is the Lisp equivalent of printf, only (MUCH!) more powerful, of course. We will talk about format in detail later, but for now, format t prints to standard output, and ~a is the control sequence for printing in "human readable" format.
Lisp can represent Unicode characters, but Emacs will only gracefull display the ones in the Latin-1 encoding (á and ð). You can try others (e.g., #\greek_capital_letter_sigma) but Emacs won't handle them as gracefully. However, you can see the Unicode using char-code:
(char-code #\greek_capital_letter_sigma)
For character comparison, use char=, char<, char>.
See §13.1.1 for additional useful functions.
Strings
Strings are also self-evaluating atoms, denoted by a series of characters between double quotes.
Constructing strings:
"my string" (char "my string" 0) (char "my string" 2) "string with \" in it" (char "string with \" in it" 11) (char "string with \" in it" 12) (char "string with \" in it" 13) (format t "~a" "string with \" in it") (string #\latin_small_letter_a_with_acute) (string-capitalize "david.r.pierce") (string-trim "as" "sassafras")
Comparing strings:
(string= "david pierce" "David Pierce") (string-equal "david pierce" "David Pierce") (string< "David Pierce" "Stu Shapiro") (string/= "foobar" "foofoo")
Strings as sequences:
(length "my string") (length "\\") (format t "~a" "\\") (subseq "my string" 3) (subseq "my string" 3 6) (position #\space "my string") (position #\i "David Pierce") (position #\i "David Pierce" :start 5) (search "pi" "david pierce and stu shapiro") (search "pi" "david pierce and stu shapiro" :start2 10) (concatenate 'string "foo" "bar") (concatenate 'string "d" (string #\latin_small_letter_a_with_grave) "v" (string #\latin_small_letter_i_with_acute) "d")
Sequences comprise several types that act as ordered containers of elements (including lists and arrays as well as strings). Each of these functions accepts sequence arguments. We'll talk about sequences more later.
Exercise: Define (string-1+ s) to construct a new string by adding 1 to the character code of each character in its argument. For example, (string-1+ "a b c") => "b!c!d".
\
| ... |
symbol-name
symbol-value
symbol-function
symbol-package
symbol-plist
'expression
always evaluates to
expression
rather than the value of
expression
average
(type-of 'average) (symbol-name 'average) (type-of (symbol-name 'average)) (symbol-function 'average) #'average (type-of #'average) (type-of (type-of #'average)) (function-lambda-expression #'average)
Load a compiled version of the file (e.g. with C-c C-b
)
Try:
#'average (function-lambda-expression #'average)
*common-lisp*
buffer, and
type C-x 1
to expand it.average
in your
*common-lisp*
buffer, and type C-c .
(Really type the dot.)
The process of installing a symbol in a catalogue is called
interning it,
and a symbol that's been so installed is called an interned symbol.
A package
is a catalogue (map) of symbol name => symbol. I.e., a "name
space".
There's always a current package which the Lisp reader uses to look up
symbol names.
Try typing *package*
to the Lisp
Listener.
Lisp packages have nothing to do with file directories or files, though the usual style is to have a file "in" only one package.
A symbol that's interned in a package may be internal or
external in that package, and that package is
considered the symbol's home package.
Find the home package of a symbol by evaluating (symbol-package
symbol)
Try (symbol-package 'average)
and (symbol-package
'length)
Every package has a name, and may have one or more nicknames.
Try: (package-name (symbol-package 'average))
and (package-nicknames (symbol-package 'average))
Mappings between packages and their (nick)names:
(find-package package-name-or-symbol)
(package-name package)
(package-nicknames package)
Evaluate (describe 'average)
You should be able to
understand all or most of what's printed.
Evaluate (describe 'length)
Notice how many packages
there are.
Put your cursor on a symbol in either the Lisp listener, or in a
file of Lisp source code, and type C-c d
, then
RET
in the minibuffer.
Try (documentation 'average 'function)
Symbol Completion: C-c TAB
You can make a symbol external in its home package by export
ing
it.
Try (export 'average)
Describe average
again.
You can change packages in the Lisp Listener by entering :pa
name-or-symbol
Try :pa lisp
Note the
prompt.
You can refer to a symbol whose home package is p
from
some other package whether or not it is external in p
.
To refer to an external symbol s
in package
p
type p:s
To refer to an internal symbol s
in package
p
type p::s
Try:
Notice the printed representation Lisp chooses for these symbols.'cl-user::discrim 'cl-user::average 'cl-user:average 'cl-user::length 'discrim
"discrim"
in the common-lisp
package.
Enter :pa user
to return to the
common-lisp-user
package.
Try:
It should not be confusing that'cl-user::discrim 'cl::discrim (symbol-name 'discrim) (symbol-name 'cl::discrim) (string= (symbol-name 'discrim) (symbol-name 'cl::discrim)) (eql 'discrim 'cl::discrim)
discrim
and
cl::discrim
are two different symbols that have
the same name.
Two Special Packages
Every symbol in the keyword package is external and evaluates to itself.
It is entered with an empty package name and a single :
Try (describe :foo)
If the reader reads #:s
, it will create an
ininterned symbol named "s"
, i.e., one that
is not interned in any package.
An uninterned symbol can never by
found by the Lisp reader, and so will never be eql
to any
separately read symbol, even one with the same name.
Try:
Evaluate(describe '#:foo) (eql '#:foo '#:foo) (string= (symbol-name '#:foo) (symbol-name '#:foo))
(gensym)
. gensym
creates new uninterned symbols.
Defining Packages
The simplest way to define a package is by evaluating (defpackage
package-name)
, where package-name
,
which is not evaluated, is either a string, which will be the
package's name, or a symbol whose name will be used for the name of
the package. We recommend using a keyword symbol, such as
(defpackage :test)
.
Look at the xemacs buffer in which you've been putting the
exercises. Notice "pkg:user
" in the mode line.
Enter (defpackage :test)
as the first form in this
file, right after your initial comments.
We want the symbols in this file to be interned into the
test
package. So, as the Lisp reader is reading this
file, we want to change the package to the test
package
right after that package has been defined. We'll do this by calling
the in-package
macro, which is what the Listener command :pa
calls. The
in-package
macro takes a string or symbol as its
argument, but doesn't evaluate it. We recommend using a symbol of the
keyword package, so put (in-package :test)
right after
the defpackage
form, and save the file.
Notice the package in the mode line hasn't changed (yet). Kill the
buffer, and reload the file into xemacs. Notice that the package in
the mode line is correct. This is important, because that is the
package xemacs will use when you do C-c C-b
or C-u
C-c C-b
.
Edit your file by changing ":test
" to
":exercises
" in both places. Save the file. While your
cursor is still in the buffer containing the file, enter M-x
fi:parse-mode-line-and-package
. (Use symbol completion.)
Notice that the package in the mode line has now been updated
properly.
Xemacs sets the package of a buffer by finding the first occurrence
of a call to in-package
in the file. You should not have
more than one call to in-package
in any file.
Exercise: Exit from Lisp, and rerun it. Load your file of function definitions. What package is the Listener now in? Test some of your functions.
When Lisp loads a file, it stores the value of
*package*
, and restores it after loading the file.
That's why you didn't have to call in-package
after
loading the file to return to the user
package.
Question: Was the Lisp reader "in" the
exercises
package when it read every form in your file?
Make the symbols defining the functions in your
exercises
package external by exporting them:
Change the form
(defpackage :exercises)
to
Save this version of the file, exit Lisp, run Lisp again, load your source file, and try using the functions from the(defpackage :exercises (:export #:average #:discrim #:fact #:quad-roots #:string-1+))
user
package.
Using Packages
One package may use another package. In that case, in the using package every exported symbol of the used package may be referred to without a package qualifier.
For example, the common-lisp-user
package uses the
common-lisp
package, which is why you can enter
common-lisp
symbols such as
common-lisp:length
without typing the package
qualifier.
To see this, evaluate (package-use-list
:user)
.
The excl
package contains many symbols used in the
Allegro Common Lisp implementation that are not in the standard
common-lisp
package, such as excl:exit
.
What packages does the exercises
package use?
Every package that's defined uses the common-lisp
package
by default. This is important so that pre-defined Lisp functions may
be accessed easily.
In the Lisp Listener, in the user
package, evaluate
the form (use-package :exercises)
. Now try using the
functions you've defined without the package qualifier.
You may experiment on your own with defining a package and
explicitly listing other packages for it to use. If you explicitly
indicate any packages to use, you must explicitly list the
common-lisp
package also.
Shadowing Symbols
Exercise: In your exercises file, define
the function last
to take a string and return its last
character.
You can't, because last
is the name of a function in
the common-lisp
package, and you're not allowed to
redefine these functions. (Note: functions aren't in packages.
Reword that statement more carefully.)
There are a lot of symbols in the common-lisp
package.
Must you avoid all of them when naming your functions? No!
Change the package in the Lisp Listener to the
exercises
package, and shadow cl:last
by
evaluating (shadow 'last)
, and then type your definition
of last into the Listener. Test it.
Add your definition of last
to your source file, and
add the form (:shadow cl:last)
to your
defpackage
form. Also add your last
symbol
to those being exported.
Exit Lisp. Rerun it. Load your source file. Test your
last
function.
Try to have the user
package use the
exercises
package. There's a conflict. Only one symbol
with a given name may be accessible in any package without
qualification. You have to choose which one.
Lists and Conses
Lists are a fundamental data structure in Lisp, from which the language derives its name (LISt Processing).
A list is an object that holds a sequence of elements, which may be (references to) any Lisp objects. The syntax for lists is (a b c ...). Lists are created by list.
'() '(1 2 3) (list 1 2 3)
Notice what Lisp prints for '() -- nil. The symbol nil also represents the empty list, as well as false.
Exercise: Construct the list containing the two lists (1 2 3) and (4 5 6).
Accessing elements:
(first '(1 2 3)) (second '(1 2 3)) (third '(1 2 3)) (nth 5 '(1 2 3 4 5 6 7 8 9 10)) (rest '(1 2 3)) (rest (rest '(1 2 3))) (nthcdr 0 '(1 2 3 4 5 6 7 8 9 10)) (nthcdr 5 '(1 2 3 4 5 6 7 8 9 10))
(endp '()) (endp '(1 2 3)) (endp nil) (endp ()) (listp '()) (listp '(1 2 3)) (eql '(1 2 3) '(1 2 3)) (equal '(1 2 3) '(1 2 3)) (length '(1 2 3)) (append '(1 2 3) '(4 5 6)) (member 3 '(1 2 3 4 5 6)) (last '(1 2 3 4 5 6)) (last '(1 2 3 4 5 6) 3) (butlast '(1 2 3 4 5 6)) (butlast '(1 2 3 4 5 6) 3)
Lists are also sequences.
Exercise: Write a function (reverse l) which returns a list containing the elements of list l in reverse order. (Common Lisp already includes the reverse function, so you must deal with the name conflict again.)
The basic building block of a list is called a "cons". A cons is an object containing two elements. The elements are called the car and the cdr (for historical reasons). The syntax of a cons is (car . cdr). You can picture a cons such as (1 . 2) as:
Conses are used to construct (linked) lists in the usual way.
When we use conses to implement lists, we will often refer to the two elements as the first and the rest, or the head and the tail. A list whose final cdr is not the empty list () is called a "dotted list" (e.g., (1 2 . 3)). A "proper list" has () (i.e., nil) as its final cdr. The function cons constructs cons cells. Since lists are implemented using conses, it follows that cons is also the function to add elements to the front of a list.
Working with conses:
(cons 1 2) (cons 1 nil) '(1 . nil) (cons 1 '(2 3)) (consp '(1 . 2)) (car '(1 . 2)) (cdr '(1 . 2)) (first '(1 . 2)) (rest '(1 . 2))
Incidentally, conses can also be used to build binary trees.
Exercise: Construct the binary tree pictured above.
Exercise: Define a function (flatten2 binary-tree) that returns the list of elements in binary-tree in order.
Moreover, proper lists can be used to build variable-arity trees -- for example, ((a (b) c) (d ((e)) () f)).
One-Branch Conditionals
If may be used without an else expression. In this case, the else expression defaults to nil. However, the preferred style in such a case is to use when and unless. In particular, (when test expression...) evaluates the test and, if its result is true, evaluates the expressions in order, using the result of the last expression as the final result. If the test fails, the result is nil. Similarly, (unless test expression...) evaluates its expressions if the result of the test is false.
Incidentally, many Lisp control structures -- like Lisp programs -- allow a sequence of expressions and take their result from the last expression. These include defun, when, unless, and cond, which we will see next. Control structures that do this are said in the language specification to use an "implicit progn". Perhaps later we'll mention where this terminology originates.
One-branch conditionals are particularly useful when the default value of a computation is nil. For example:
(defun member (x list) "Returns true if x is a member of list." (when list (or (eql x (first list)) (member x (rest list)))))
Exercise: Write a function (get-property x list) which returns the element of list immediately following x, or nil if x does not appear in list. For example, (get-property 'name '(name david office 125)) => david. (You might take advantage of the fact that Lisp's builtin member function does not just return t when x is in the list. You can write this without using when, but just for fun, use it anyway.) A list of the form used by this function is traditionally called a property list. Similar builtin functions are getf and get-properties, although they take slightly different arrangements of arguments.
Multi-Branch Conditionals
The form of the multi-branch conditional is:
(cond (expression11 expression12 ...) (expression21 expression22 ...) ... (expressionn1 expressionn2 ...))
The expressioni1
are evaluated
starting at i = 1 until one of them evaluates to any
non-null
value. If so, the rest of the expressions in
that group (if any) are evaluated, and the value of the last one
evaluated becomes the value of the cond
. If all of the
expressioni1
evaluate to
nil
, then the value of the cond
is
nil
. As is often the case, the value of a Lisp
expression is the value of the last subexpression evaluated under its
control.
Most frequently, cond
is thought of as:
(cond (test1 expression1 ...) (test2 expression2 ...) ... (testn expressionn ...))
The last test may be t, when it is to be considered the default clause.
(defun elt (list index) "Returns the index'th element of list, or nil if there isn't one." (cond ((endp list) nil) ((zerop index) (first list)) (t (elt (rest list) (1- index)))))
Exercise: Define (flatten tree) to take an argument list representing a variable-arity tree, and return a list of all the atoms which appear in it. For example (flatten '((a (b) c) () (((d e))))) => (a b c d e).
Another convenient use of the multi-branch conditional is encapsulated in the case form. Case implements branching on the value of an expression indexed by literal constants (similar to "switch" in other languages). For example, suppose we ask someone to guess a number:
(case (read) (2 "sorry, too low") (3 "right on!") (4 "sorry, too high") (t "way off?!"))
A case form is more or less equivalent to a cond form as follows:
(case expression (literal1 result1) (literal2 result2) ... (literaln resultn))≡
(cond ((eql 'literal1 expression) result1) ((eql 'literal2 expression) result2) ... ((eql 'literaln expression) resultn))
except that expression is only evaluated once. As with cond, the last clause may be labeled with t to indicate that it is the default clause. Also notice the quote in the cond, as the literals are not evaluated in a case form.
Unlike, C's switch
statement, Lisp's case
can handle multiple cases giving the same answer without the need for
a break
. For example,
(case (read) ((#\a #\e #\i #\o #\u) 'vowel) (#\y 'sometimes\ vowel) (t 'consonent))
Local Variables
Remember the quad-roots function from a few weeks ago?
(defun quad-roots (a b c) "Returns the two roots of the equation ax^2 + bx + c." (values (/ (+ (- b) (discrim a b c)) (* 2 a)) (/ (- (- b) (discrim a b c)) (* 2 a))))
It would be preferable to avoid recalculating all the subexpressions of this formula for the two returned values. Local variables are introduced by let.
(defun quad-roots (a b c) "Returns the two roots of the equation ax^2 + bx + c." (let ((-b (- b)) (d (discrim a b c)) (2a (* 2 a))) (values (/ (+ -b d) 2a) (/ (- -b d) 2a))))
The general form of a let expression is:
(let ((v1 e1) (v2 e2) ... (vn en)) expression ...)
The variables v1 through vn are bound to the results of the expressions e1 through en. These bindings have effect throughout the body expressions. As usual, the result of the let expression is the result of the last body expression.
Let bindings are lexically scoped:
(let ((x 1)) (list (let ((x 2)) x) (let ((x 3)) x)))
Let bindings are performed in parallel:
(let ((x 3)) (let ((x (1+ x)) (y (1+ x))) (list x y)))
A variant of let called let* performs the bindings sequentially.
(let ((x 3)) (let* ((x (1+ x)) (y (1+ x))) (list x y)))
defun
form is called a lambda list. The lambda
lists we have seen so far contain only required parameters, but there
are actually a total of five kinds of parameters that might be
included, in the order listed below.
&optional
. Each optional parameter may be
If there are more actual arguments than required parameters, the extras are bound to the optional parameters in left-to-right order. If there are extra optional parameters, they are bound to the value ofvar
(var default-value)
or(var default-value supplied-p)
default-value
, if it is present, else to
nil
. If supplied-p
is present and
there is an actual argument for the optional parameters, it is bound
to t
, otherwise it is bound to nil
.last
takes an optional argument.
(defun testOpt (a b &optional c (d 99 dSuppliedp)) (list a b c d (if dSuppliedp '(supplied) '(default)))) (testOpt 2 3) (testOpt 2 3 4 5)
reverse
/reverse1
pair as the single function
reverse
, that takes one required argument and one
optional argument.
&rest
, it must be followed by a
simple parameter symbol, and that symbol is bound to a list of the
values of all the actual arguments that follow the required arguments.-
has one required parameter
and a rest parameter, so it takes one or more arguments.
and
has only a rest parameter, so it
takes zero or more arguments.
(defun testRest (a b &rest c) (list a b c)) (testRest 1 2) (testRest 1 2 3 4 5 6)
Exercise: The function union
takes two lists and returns a list of all the elements that are in
either argument list. Try it. Define your own union
function, in your package, that takes zero or more argument lists,
and, using cl:union
returns the union of all its
argument lists.
Bonus fact: The Lisp function
apply
takes two arguments: a function, and a list of
arguments for the function. apply
returns the value of
applying the function to those arguments.
Try:
(apply #'cons '(a b)) (apply #'+ '(1 2 3 4))
Keyword parameters are optional, but their arguments may appear in any order, and any one may be supplied or omitted independently of the others.
Keyword parameters follow the lambda list keyword
&key
. Each keyword parameter may be
A keyword parametervar
(var default-value)
or(var default-value supplied-p)
var
is used in the body of the
function as you would expect, but in the function call, a keyword
argument is specified by preceding it with a symbol in the keyword
package whose name is the same as the name of the
var
, that is, :var
.Exercise:
(defun testKey (a &key oneKey (twoKey 99 2Suppliedp)) (list a oneKey twoKey (if 2Suppliedp '(supplied) '(default)))) (testKey 2) (testKey 2 :oneKey 5) (testKey 2 :twoKey 5) (testKey 2 :twoKey 10 :oneKey 5)
member
has two required parameters and three keyword parameters.(member '(a b) '((a c) (a b) (c a))) (member '(a b) '((a c) (a b) (c a)) :test #'equal) (member 'a '((a c) (a b) (c a))) (member 'a '((a c) (a b) (c a)) :key #'second) (member 'a '((a c) (a b) (c a)) :key #'second :test-not #'eql)
cl:union
also takes three keyword parameters. Modify your arbitrary-argument
union
to accept the same three keyword parameters, and
pass on to cl:union
those, but only those, that are
passed to your function.
Bonus fact: The function identity
returns the value of its argument.
&aux
,
and are a list of local variables with initializations. The
definition
is exactly equivalent to(defun (var1 ... varn &aux avar1 ... avarm) body)
(defun (var1 ... varn) (let* (avar1 ... avarm) body))
Exercise:
(defun test (x &aux (x (1+ x)) (y (1+ x))) (list x y)) (test 3)
quad-roots
function to use aux
parameters instead of let variables.
There are a number of iteration constructs in Lisp, but only one that you need to know -- the loop facility. Loop subsumes all the other iteration constructs; and it is easier to read and more flexible.
The simplest kind of loop takes the form: (loop expression...). This repeatedly executes the expressions. The loop is essentially infinite, but you can use return to end it.
An "extended loop" consists of a sequence of loop clauses. Here is a simple example
(loop for i from 1 to 10 do (print (* i i)))
which comprises two clauses: (1) for i from 1 to 10; and (2) do (print (* i i)).
As you can see, loops don't look like normal Lisp code. Normal Lisp code uses list forms to indicate the structure of the program. In constrast, since loops are more syntactically complex, a number of "loop keywords" are employed to indicate their structure. (Loop "keywords" are not really keyword symbols, but rather syntactic keywords like the keywords of Java, or the keywords of lambda lists -- &rest, etc.) Each kind of loop clause is introduced by a different keyword. Other keywords are used to indicate the internal structure of a compound clause.
There are seven kinds of loop clauses -- iteration control clauses, termination tests, value accumulation clauses, unconditional execution clauses, conditional execution clauses, initial-final clauses, local variable clauses.
Iteration control clauses are introduced by the keyword for, determine how a loop variable is "stepped" between each iteration, and arrange for the loop to terminate when finished stepping. There are seven kinds of iteration control clauses. Some of these clauses iterate over common data structures, one iterates numerically, and one is a general-purpose clause.
Numeric ranges: for var from start {to | upto | below | downto | above} end [by incr]
(loop for i from 99 downto 66 by 3 do (print i))
List elements: for var in list [by step-fun]
(loop for x in '(a b c d e) do (print x)) (loop for x in '(a b c d e) by #'cddr do (print x))
An interesting aspect of the loop facility is that it allows destructuring bindings.
;; Ignore the format magic used in the following examples for now. ;; We'll talk about format later. (loop for (l n) in '((a 1) (b 2) (c 3) (d 4) (e 5)) do (format t "~a is the ~:r letter~%" l n)) (loop for (first . rest) in '((42) (a b) (1 2 3) (fee fie foe fum)) do (format t "~3a has ~d friend~:*~p~%" first (length rest)))
List tails: for var on list [by step-fun]
(loop for x on '(a b c d e) do (print x)) (loop for x on '(a b c d e) by #'cddr do (print x))
And with destructuring again:
(loop for (x y) on '(a b c d e f) by #'cddr do (print (list x y)))
Vector elements: for var across vector
(loop for c across "david" do (print (char-upcase c)))
Hash table elements: for var being each {hash-key | hash-value} of hash-table
Package symbols: for var being each {present-symbol | symbol | external-symbol} [of package]
(loop for x being each present-symbol of *package* do (print x))
Anything: for var = expression [then expression]
(loop for x from 0 below 10 for y = (+ (* 3 x x) (* 2 x) 1) do (print (list x y))) (loop for l in '(a b c d e) for m = 1 then (* 2 m) do (format t "the bit mask for ~a is ~d~%" l m)) (loop for prev = #\d then next for next across "avid" do (format t "~a came before ~a~%" prev next))
Iteration control clauses are normally performed sequentially. Stepping can be performed in parallel by connecting the clauses with and rather than for.
Normally, loop returns nil. However, value accumulation clauses can determine other values to return.
List accumulation clauses construct a result list: {collect | append} expression [into var].
(defun explode (string) (loop for c across string collect c)) (defun flatten (tree) (if (listp tree) (loop for child in tree append (flatten child)) (list tree))) (loop for r on '(a b c d e) collect (length r) append r)
Numeric accumulation clauses compute a result value: {count | sum | minimize | maximize} expression [into var].
(loop for l in '((1 2 3) () (fee fie foe fum) () (a b c d e)) for n = (length l) count l into count sum n into sum minimize n into min maximize n into max do (print (list count sum min max))) (loop for l in '((1 2 3) () (fee fie foe fum) () (a b c d e)) for n = (length l) maximize n into max sum max) (loop for l in '((1 2 3) () (fee fie foe fum) () (a b c d e)) count l count l sum (length l))
(loop initially (format t "testing") repeat 10 do (sleep 0.5) (format t ".") finally (format t "done~%"))
The finally clause is especially useful for returning a value computed by the loop.
(loop for l in '((1 2 3) () (fee fie foe fum) () (a b c d e)) for n = (length l) count l into count sum n into sum minimize n into min maximize n into max finally (return (values count sum min max))) ;; just to mess with you (loop repeat 5 collect (copy-list foo) into foo finally (return foo))
Exercise: Rewrite the fact function using loop. Rewrite the fibonacci function.
Unconditional execution clauses
You've already seen one of the two unconditional execution clauses.
The do, initially, and finally clauses are the only places in loop where a sequence of expressions is allowed after a loop keyword. As usual, these are executed sequentially.
The form of a conditional execution clause is
{if | when | unless} test selectable-clause {and selectable-clause}* [else selectable-clause {and selectable-clause}*] [end]
where a selectable-clause is a value accumulation clause or unconditional or conditional execution clause.
(loop for x in '((1 2 3) 4 (5 6) 7 8) if (listp x) sum (apply #'* x) else sum x)
Exercise: Rewrite the get-property function using loop. Explain how your new implementation improves over the old one, assuming that even elements of a property list are meant as keys and odd elements as values.
(defun power (x n) (loop repeat n for y = x then (* y x) finally (return y))) (defun user-likes-lisp-p () (loop initially (format t "Do you like Lisp? ") for x = (read) until (member x '(y n)) do (format t "Please type either `y' or `n'. ") finally (return (eql x 'y)))) (defun composite-p (n) (loop for k from 2 below (sqrt (1+ n)) thereis (when (zerop (nth-value 1 (floor n k))) k))) ;; just for fun (defun prime-factorization (n) (let ((k (composite-p n))) (if k (append (prime-factorization k) (prime-factorization (floor n k))) (list n))))
Exercise: Define a function (split list splitters) that returns a list of chunks of the list that occur between elements of splitters. For example, (split '(1 2 3 4 5 6 7 8 9) '(3 6)) => '((1 2) (4 5) (7 8 9)). (Hint: Use a nested loop.)
Two other ways of terminating a loop should be mentioned. The form (return [value]) immediately terminates the loop and returns the value. The form (loop-finish) terminates the loop, executes the finally clauses, and returns any accumulated value.
A loop can be named -- (loop named name clauses...); and such a loop can be terminated using (return-from name [value]) similarly to return. (More precisely, a loop establishes an implicit block of the same name, or nil.)
(loop with s = "david pierce" for prev = (char s 0) then next for next across (subseq s 1) do (format t "~a came before ~a~%" prev next))
With clauses are normally initialized sequentially. Variables can be initialized in parallel by connecting the clauses with and rather than with.
I'll conclude this lesson with a few additional points about loops.
As we have seen, termination conditions can arise in several places -- iteration control clauses, termination test clauses, and even uses of return and loop-finish. Just to clarify, a loop terminates when its first termination condition is satisfied. Depending on the type of termination, the loop may or may not return a value, and may or may not execute the finally clauses.
Although loop is quite flexible about the order of clauses, the order is not completely free. The general rule is the "variable" clauses come before "action" clauses. The "variable" clauses are the interation control and with clauses. The "action" clauses are the execution, value accumulation, and termination test clauses. Initial-final clauses may occur anywhere.
Usual style is to name globals(
defconstant
name initial-value [documentation])
Illegal to change value.
(
defparameter
name initial-value [documentation])
(
defvar
name [initial-value [documentation]])
Won't reinitialize the variable.
*var*
Try:
(defconstant *Lab* 'Baldy\ 19 "Where we meet.") *Lab* (defconstant *Lab* 'Baldy\ 21 "Where we meet.") *Lab* (defparameter *Time* "TTh 1:30-2:30" "Time of our meeting") *Time* (defparameter *Time* "MTh 10:30-1:30" "Time of our meeting") *Time* (defvar *Attendance* 20 "The number of students attending") *Attendance* (defvar *Attendance* 6 "The number of students attending") *Attendance*
Try:(
setsymbol value)
Evaluates both arguments.
(
setq{symbol value}*)
Doesn't evaluatesymbol
. Old fashioned.
(
setf{place value}*)
Uses l-value ofplace
. Sequential.
(
psetf{place value}*)
Uses l-value ofplace
. Parallel.
(setf *Lab* 'Baldy\ 19) (setf *Time* "TTh 10:30-1:30" *Attendance* 10) *Time* *Attendance* (setf x 3 y 5) ; Don't assign to new global variables in a function body x y (psetf x y y x) x y
May be a symbol, or a wide variety of specifiers of generalized references.
For example:
(setf x '(a b c d e)) (setf (second x) 2) x (setf addresses (make-hash-table)) (setf (gethash 'Stu addresses) 'shapiro@cse.buffalo.edu) (setf (gethash 'David addresses) 'drpierce@cse.buffalo.edu) (setf (gethash 'Luddite addresses) nil) (gethash 'David addresses) (gethash 'Stu addresses) (gethash 'Luddite addresses) (gethash 'Bill addresses)
But be careful:
(defun goodTimers (folks) (append folks '(had a good time))) (setf list1 (goodTimers '(Trupti Mike and Fran))) (setf (seventh list1) 'bad) list1 (goodTimers '(Jon Josephine and Orkan))
And don't get carried away. Experienced Lispers use setf
very infrequently.
* | The last object returned by the Lisp listener. |
---|---|
** | The second-last object returned by the Lisp listener. |
*** | The third-last object returned by the Lisp listener. |
*package* | The current package. |
*print-base* | Numeric base to be used when printing numbers. |
*read-base* | Numeric base to be used when reading numbers. |
| |
| |
tpl:*print-length* | The maximum length of
lists printed at the top level; if nil ,
no limit. |
tpl:*print-level* | The maximum depth of
lists printed at the top level; if nil ,
no limit. |
Exercise: Turn the Lisp listener into a converter from hexidecimal notation to binary notation. Then put it back.
Sequential Execution
Now that we've looked at assignment, we might as well see the other essential imperative construct -- sequencing. Actually there's nothing new here, because many Lisp forms allow a sequence of expressions in the "body" of the form. This is true of defun, cond, and let, for example.
Remember also that we told you in passing that the sequence of expressions forming the body of such a construct was called an "implicit progn". That's because progn is the Lisp expression for enclosing an explicit sequence of expressions. The result of a progn is the value of the last expression; it discards the results of the others.
You won't need to use progn very much, since most of the usual hangouts for sequences of expressions are already implicit progns. However, there are a couple of interesting variations of progn that are occasionally handy, namely prog1 and prog2.
(prog1 1 2 3) (prog2 1 2 3) (progn 1 2 3)
Functions
We already know quite a bit about functions -- at least, named functions.
What can we do with function objects in Lisp?
Some examples we've already seen:
(member '(a c) '((a b) (a c) (b c)) :test #'equal) (loop for x in '(a b c d e) by #'cddr do (print x))
Some new examples:
(funcall #'cons nil nil) (setf some-functions (list #'third #'first #'second)) (funcall (first some-functions) '(a b c)) (defun multicall (list-of-functions &rest arguments) "Returns a list of results obtained by calling each function in LIST-OF-FUNCTIONS on the ARGUMENTS." (loop for f in list-of-functions collect (apply f arguments))) (multicall (list #'third #'second #'first) '(a b c))
Exercise: Define a function (tree-member item tree &key (key #'identity) (test #'eql)) that returns the subtree of a labeled tree rooted at item, like member does for lists. A labeled tree node is represented as (label . children) where children is a list of nodes. Leaf nodes have empty child lists. An item is regarded as equal to a label of tree when (test item (key label)) is true. For example:
(tree-member "feline" '("animal" ("mammal" ("feline" ("lion") ("tiger") ("kitty")) ("rodent" ("squirrel") ("bunny") ("beaver"))) ("bird" ("canary") ("pigeon")) ("reptile" ("turtle") ("snake"))) :test #'string=) ==> ("feline" ("lion") ("tiger") ("kitty"))
Since function objects can be used so flexibly in Lisp, it makes sense that we should be able to create a function without having to define a named function; that is, use a throwaway function instead of a permanent defun. That's what lambda is for. A "lambda expression" can be used in place of a function name to obtain a function.
#'(lambda (x) (+ x 1)) ((lambda (x) (+ x 1)) 42) (funcall #'(lambda (x) (+ x 1)) 42)
Note that
((lambda lambda-list . body) . arguments) == (funcall #'(lambda lambda-list . body) . arguments).
In fact, the function form isn't really necessary, because lambda is set up so that
(lambda lambda-list . body) == #'(lambda lambda-list . body).
Lambda functions are actually closures, which means that they comprise not only their code, but also their lexical environment. So they "remember" variable bindings established at the time of their creation.
(defun make-adder (delta) (lambda (x) (+ x delta))) (setf f (make-adder 13)) (funcall f 42) (funcall (make-adder 11) (funcall (make-adder 22) 33))
Exercise: Define a function (compose f g) that composes functions f and g. Assume that f composed with g is defined as (f • g)(x) = f(g(x)). Try (funcall (compose #'char-upcase #'code-char) 100).
Mapping
It is common to want to apply a function to every element of a list and obtain the results of each of the calls. This operation is called mapping. Lambda expressions tend to be quite useful in mapping.
(mapcar #'(lambda (s) (string-capitalize (string s))) '(fee fie foe fum)) (maplist #'reverse '(a b c d e)) (mapcar #'(lambda (s n) (make-list n :initial-element s)) '(a b c d e) '(5 2 3 7 11)) (mapcan #'(lambda (s n) (make-list n :initial-element s)) '(a b c d e) '(5 2 3 7 11)) (mapcon #'reverse '(a b c d e))
Sequences
Sequence is the common superclass of lists and vectors (i.e., one-dimensional arrays) -- one-dimensional ordered collections of objects. Sequences also support mapping.
(map 'list #'(lambda (c) (position c "0123456789ABCDEF")) "2BAD4BEEF") (map 'string #'(lambda (a b) (if (char< a b) a b)) "David Pierce" "Stu Shapiro")
Here are examples of other useful functions on sequences. Many of these take functions as arguments.
(count-if #'oddp '(2 11 10 13 4 11 14 14 15) :end 5) (setf x "David Pierce") (sort x #'(lambda (c d) (let ((m (char-code c)) (n (char-code d))) (if (oddp m) (if (oddp n) (< m n) t) (if (oddp n) nil (< m n)))))) ;; note that SORT does destructive modification x (find-if #'(lambda (c) (= (* (first c) (first c)) (second c))) '((1 3) (3 5) (5 7) (7 9) (2 4) (4 6) (6 8))) (position-if #'(lambda (c) (= (* (first c) (first c)) (second c))) '((1 3) (3 5) (5 7) (7 9) (2 4) (4 6) (6 8))) (reduce #'+ '(1 2 3 4)) (reduce #'list '(a b c d e)) (reduce #'list '(a b c d e) :initial-value 'z) (reduce #'list '(a b c d e) :from-end t) (reduce #'append '((a b) (c d) (e f g) (h) (i j k)))
Exercise: Suppose you are given a list of headings for the columns of a table -- for example, ("Function " "Arguments " "Return values " "Author " "Version "). The size of the columns are determined by the length of these headings. Write an expression to compute the number of spaces to insert to place text into the nth column of the table.
Input/Output
I/O in Lisp is based on streams. A stream is a source or destination for characters or bytes. For example, streams can be directed to or from files, strings, or the terminal. Output functions (e.g., format and print) and input functions (e.g., read) normally take stream arguments; although frequently the stream argument is optional. Several streams are available when Lisp starts up, including *standard-input* and *standard-output*. If the session is interactive, both of these are the same as *terminal-io*.
The basic output functions for streams are write-char and write-line. The basic input functions are read-char and read-line.
File streams are created by the open function. However, it is more convenient to use the with-open-file form, which ensures that the file is closed regardless of whether control leaves normally or abnormally.
(with-open-file (output-stream "/tmp/drpierce.txt" ; put your name here :direction :output) (write-line "I like Lisp" output-stream)) (with-open-file (input-stream "/tmp/drpierce.txt" :direction :input) (read-line input-stream)) (with-open-file (output-stream "/tmp/drpierce.txt" :direction :output :if-exists :supersede) (write-line "1. Lisp" output-stream)) (with-open-file (output-stream "/tmp/drpierce.txt" :direction :output :if-exists :append) (write-line "2. Prolog" output-stream) (write-line "3. Java" output-stream) (write-line "4. C" output-stream)) ;; read lines until eof (with-open-file (input-stream "/tmp/drpierce.txt" :direction :input) (loop for line = (read-line input-stream nil nil) while line collect line))
Similarly, a string stream is usually manipulated using with-output-to-string and with-input-from-string.
(with-output-to-string (output-stream) (loop for c in '(#\L #\i #\s #\p) do (write-char c output-stream))) (with-input-from-string (input-stream "1 2 3 4 5 6 7 8 9") (loop repeat 10 collect (read-char input-stream)))
Although the basic I/O functions are available, you will normally invoke the higher-level capabilities of the Lisp printer and the Lisp reader. We discuss the printer and reader in the following sections.
Streams are closed using close. Other stream functions include streamp, open-stream-p, listen, peek-char, clear-input, finish-output.
The Printer
The standard entry point into the printer is the function write; and prin1, princ, print, and pprint are wrappers for certains settings of write. The optional output stream argument of each of these functions defaults to standard output. Another useful set of print functions is write-to-string, prin1-to-string, and princ-to-string.
(setf z '("animal" ("mammal" ("feline" ("lion") ("tiger") ("kitty")) ("ursine" ("polar bear") ("teddy bear")) ("rodent" ("squirrel") ("bunny") ("beaver"))) ("bird" ("canary") ("pigeon")) ("reptile" ("turtle") ("snake")))) (prin1 z) ;; same as (write z :escape t) (princ z) ;; same as (write z :escape nil :readably nil) (write z :escape nil :pretty t :right-margin 40) (write-to-string z :escape nil :pretty nil)
A more sophisticated and flexible aspect of the printer is the format function -- (format destination control-string argument...). This function consults the control-string to determine how to format the remaining arguments (if any) and transfers the output to destination.
If destination is: | then the output: |
t | appears on the standard output |
a stream | appears on that stream |
nil | is returned as a string |
The control string consists of simple text, with embedded format control directives. Some of the simpler, more commonly used directives are summarized below.
~W | format as if by write; any kind of object; obey every printer control variable |
~S | format as if by prin1; any kind of object; "standard" format |
~A | format as if by princ; any kind of object; human readable ("asthetic") format |
~D (or B, O, X) | decimal (or binary, octal, hex) integer format |
~F (or E, G, $) | fixed-format (or exponential, general, monetary) floating-point format |
~{control-string~} | format a list; repeatedly uses control-string to format elements of the list until the list is exhausted |
~% | print a newline |
~& | print a newline unless already at the beginning of the line |
~~ | print a (single) tilde |
~* | ignore the corresponding argument |
~newline | ignore the newline and any following whitespace (allows long format control strings to be split across multiple lines) |
Many format control directives accept "arguments" -- additional numbers or special characters between the ~ and the directive character. For example, a common argument allowed by many directives is a column width. See the documentation for individual directives for details about their arguments. In place of an "argument" in the control string, the character v indicates the next format argument; while the character # denotes the number of remaining format arguments.
;; format an invoice (loop for (code desc quant price) in '((42 "House" 1 110e3) (333 "Car" 2 15000.99) (7 "Candy bar" 12 1/4)) do (format t "~3,'0D ~10A ~3D @ $~10,2,,,'*F~%" code desc quant price)) (defun char-* (character number) "Returns a string of length NUMBER filled with CHARACTER." (format nil "~v,,,vA" number character "")) ;; but (make-string number :initial-element character) is better ;; format an invoice again, one-liner (format t "~:{~3,'0D ~10A ~3D @ $~10,2,,,'*F~%~}" '((42 "House" 1 110e3) (333 "Car" 2 15000.99) (7 "Candy bar" 12 1/4))) ;; comma-separated list (loop for i from 1 to 4 do (format t "~{~A~^, ~}~%" (subseq '(1 2 3 4) 0 i))) ;; comma-separated list again, but cleverer ;; (using things I didn't mention above :-) (loop for i from 1 to 4 do (format t "~{~A~#[~; and ~:;, ~]~}~%" (subseq '(1 2 3 4) 0 i))) (loop for i from 1 to 4 do (format t "~{~A~#[~;~:;,~]~@{~#[~; and ~A~:; ~A,~]~}~}~%" (subseq '(1 2 3 4) 0 i))) ;; format an invoice again, but cleverer ;; with commas in the prices (loop for (code desc quant price) in '((42 "House" 1 110e3) (333 "Car" 2 15000.99) (7 "Candy bar" 12 1/4)) do (format t "~3,'0d ~10a ~3d @ ~{$~7,'*:D~3,2F~}~%" code desc quant (multiple-value-list (floor price))))
Exercise: Define (print-properties plist &optional stream) to print a propery list to stream as shown below. The stream should default to *standard-output*.
(print-properties '(course CSE-202 semester "Summer 2004" room "Baldy 21" days "MR" time (10.30 11.30))) --> course=CSE-202 semester="Summer 2004" room="Baldy 21" days="MR" time=(10.3 11.3)
The Reader
The standard entry point into the reader is the function read. The function read-from-string is also often convenient.
(with-input-from-string (input-stream "(a b c)") (read input-stream)) (with-input-from-string (input-stream "5 (a b) 12.3 #\\c \"foo\" t") (loop repeat (read input-stream) do (describe (read input-stream))))
Below is a reader function for the property list format we used above.
(defun read-properties (&optional (input-stream *standard-input*)) "Reads a property list from INPUT-STREAM. The input should have one property-value pair on each line, in the form PROPERTY-NAME=VALUE. The PROPERTY-NAME should be a Lisp symbol. The VALUE may be any readable Lisp value." (loop for line = (read-line input-stream nil nil) while line for pos = (position #\= line) unless pos do (error "bad property list format ~s" line) collect (read-from-string line t nil :end pos) collect (read-from-string line t nil :start (1+ pos)))) (setf p1 '(course CSE-202 semester "Summer 2004" room "Baldy 21" days "MR" time (10.30 11.30))) (setf p2 (with-output-to-string (stream) (print-properties p1 stream))) (setf p3 (with-input-from-string (stream p2) (read-properties stream))) (equal p1 p3)
In practice, we might want more error checking, because read-properties happily accepts input like:
(with-input-from-string (stream "hello world = 1 2 3") (read-properties stream))
However, this entire example is a bit contrived, since if you wanted to store a property list or association list in a file (e.g., as a config file for your application), you would simply write the entire list to the file instead of formatting the data in some unreadable way. Then you could simply use the Lisp reader to reconstruct the entire list.
We will be able to work more meaningful exercises of printing and reading after we talk about Lisp "objects" -- that is, class instances. Since instances do not have a readable print syntax of their own, a common task is to concoct a readable syntax using readable objects such as lists so that instances can be written out to files and read back in. For now, the following exercise is hopefully a little more meaningful than the property list example.
Exercise: We decide to use a space-efficient file format for large, sparse arrays. The format is: dimensions default-value index1 value1 index2 value2 .... For example:
(100 100) 0 (30 30) 30 (60 60) 60
Write a function (read-sparse-array &optional input-stream) to read the sparse array format and construct the array.
Mini-Project: Write an outline formatter. Assume that the input is a sequence of lines; each line begins with n spaces (n ≥ 0) to indicated that it is a heading at outline level n. For example, here is an outline of the lecture notes on I/O:
Input/output Streams File streams String streams Stream input and output functions Other stream functions The printer Print functions Format Destinations Control directives Examples The reader
Read an outline from an input stream, automatically number and indent it, and print it to an output stream. Below is one possible output format.
I. Input/output A. Streams 1. File streams 2. String streams B. Stream input and output functions C. Other stream functions II. The printer A. Print functions B. Format 1. Destinations 2. Control directives 3. Examples III. The reader
Your outline formatter should be guided by an outline format containing a list (F0 F1 ...). Each Fn is a list of the form (width labeler), where width is the width of a label at outline level n and labeler is a function that takes a whole number and returns a label string in the style of level n. For example, the outline above was formatted using an outline format that begins as follows:
(defparameter *outline-format-1* (list (list 6 #'(lambda (n) (format nil "~@R." n))) ...
Labels of level 0 are 6 characters wide, and the labeler function produces the roman numeral labels shown above. I'll leave it to you to determine what the rest of the entries should be to produce the rest of output shown above.
First, write a function (read-outline &optional input-stream) that reads the indented outline and constructs a list of the all the lines and their level.
((0 "Input/output") (1 "Streams") (2 "File streams") (2 "String streams") (1 "Stream input and output functions") (1 "Other stream functions") (0 "The printer") (1 "Print functions") (1 "Format") (2 "Destinations") (2 "Control directives") (2 "Examples") (0 "The reader"))
Second, write a function (print-outline outline outline-format &optional output-stream) to format this list according to outline-format.
We are only going to give a simplified introduction to CLOS. There are many details we will not cover.
Many (but not all) predefined Common Lisp types are also predefined
classes. These are:
(Find the two classes with multiple parents.)
For a complete dag of the predefined classes available in ACL, select the menu item
ACL=>Composer=>Start
Composer
ACL=>Composer=>CLOS=>Show class
subclasses
t
in the minibuffer.
Example 1: Let's define a generic function that will cause some Lisp objects to identify their class:
Test id for several numbers and sequences of different subtypes.(defmethod id ((x number)) "Prints a message identifying numbers." "I'm a number.") (defmethod id ((x sequence)) "Prints a message identifying sequences." "I'm a sequence.")
As you would expect, the applicable method for the lowest possible
class is the one that is used.
Exercise: add an id
method for
some subclass of number or sequence, and test that it is used when
appropriate.
When a class C has two parent classes, and there is a generic function with a method for each one, which one is used? That is determined by C 's class precedence list. To see the class precedence list of some class, select the menu item
ACL=>Composer=>CLOS=>Inspect class
Exercise: Find a class C in the
above dag that has two parent classes. Define an id
method for each parent class. Which method is used for an object of
class C ? Inspect class C , and check that its class
precedence list is used correctly.
Example 2: Define a <
relation among numbers and
symbols, so that lists containing mixed numbers and symbols may be
ordered lexicographically. Numbers should be ordered by
cl:<
, symbols by string<
, and any
number should be <
than any symbol.
Solution:
A generic function may be used in the same ways a regular function is used. For example, we may define(defpackage :closExercises (:shadow cl:<)) (in-package :closExercises) (defmethod < ((n1 number) (n2 number)) "Returns t if the number n1 is less than the number n2; else nil." (cl:< n1 n2)) (defmethod < ((s1 symbol) (s2 symbol)) "Returns t if the symbol s1 is less than the symbol s2; else nil." (string< s1 s2)) (defmethod < ((n number) (s symbol)) "Returns t, because numbers are less than symbols." t) (defmethod < ((s symbol) (n number)) "Returns nil, because symbols are not less than numbers." nil) (defmethod < ((list1 list) (list2 list)) "Returns t if list1 is less than list2; nil otherwise." ;; Lists are ordered lexicographically according to their members. (cond ((endp list1) list2) ((endp list2) nil) ((< (first list1) (first list2)) t) ((< (first list2) (first list1)) nil) (t (< (rest list1) (rest list2)))))Exercise: Test this.
>
as
follows:
Notice that;;; First shadow cl:>. (shadow 'cl:>) ;;; Then define >. (defun > (x y) "Returns t if x is greater than y; nil Otherwise." (< y x))
>
automatically handles the same classes
as <
does.
Now, let's redo <
using defgeneric
and adding strings and lists. Strings should sort after symbols;
lists should sort after strings. That is, any number should be
<
any symbol, any symbol should be <
any string, any string should be <
any list, numbers
should be ordered by cl:<
, symbols and strings should
be ordered by string<
, and lists should be ordered as
shown above. (Do we really need to write 16 different methods?)
Solution:
(defpackage :closExercises (:shadow cl:< cl:>)) (in-package :closExercises) (defgeneric < (obj1 obj2) (:documentation "Returns t if obj1 sorts as less than obj2; nil otherwise.") (:method ((n1 number) (n2 number)) "Returns t if the number n1 is less than the number n2 using cl:<; else nil." (cl:< n1 n2)) (:method ((s1 symbol) (s2 symbol)) "Returns t if the symbol s1 is less than the symbol s2 using string<; else nil." (string< s1 s2)) (:method ((s1 string) (s2 string)) "Returns t if the string s1 is less than the string s2 using string<; else nil." (string< s1 s2)) (:method ((list1 list) (list2 list)) "Returns t if list1 is lexicographically less than list2; nil otherwise." ;; Lists are ordered lexicographically according to their members. (cond ((endp list1) list2) ((endp list2) nil) ((< (first list1) (first list2)) t) ((< (first list2) (first list1)) nil) (t (< (rest list1) (rest list2))))) (:method ((obj1 t) (obj2 t)) "Returns t if ojb1 is less than obj2 according to their types." (check-type obj1 (or number symbol string list)) (check-type obj2 (or number symbol string list)) (member obj2 (member obj1 '(number symbol string list) :test #'typep) :test #'typep))) (defun > (x y) "Returns t if x is greater than y; nil Otherwise." (< y x))New concept:check-type
.Exercises:
- Test this.
- Add characters, to sort between numbers and symbols, and, among themselves using
char<
.
(make-instance
class ...)
.
CLOS classes are defined by use of the macro defclass. (See also the HyperSpec chapter on Defining Classes.)
A class may have up to three class-options, the only one of which
we will use is :documentation
.
A class may also have a set of slots, each of which has a number of attributes specified by slot-options, which are:
:documentation
A documentation string.
:allocation
Either :instance
, meaning
that this is a slot local to each instance, or :class
,
meaning that this slot is shared among instances.
:initarg
A symbol used like a keyword in
make-instance
to provide the value of this slot.
:initform
A form, evaluated when each instance is
created, to give the value for this slot.
:reader
A symbol which is made the name of a
method used to get the value of this slot for each instance.
:writer
A symbol which is made the name of a
method used to set the value of this slot for each instance. If
setSlot
is the symbol, it is used by evaluating
(setSlot value instance)
:accessor
A symbol which is made the name of a
setf
able method used to get or set the value of this slot
for each instance.
:type
The permitted type of values of this slot.
ACL seems not to enforce this.
Even if neither a :writer
nor an
:accessor
is provided for a slot, one my set or change
the value of a slot by evaluating
(setf (slot-value object slot-name) value)
One may use
to cause the initialization of some slots of an instance after the slots specified by(defmethod initialize-instance :after ((object class) &rest args) ...)
:initarg
and :initform
have been set.
As an example, we will define some classes of weighable solids and a class of scales on which to weigh them. These are defined in the file solids.cl.
Exercises:
(removeObject scale object)
to remove an object from a scale. All slots should be maintained
properly, but removeObject
should cause an error if the
object to be removed is not on the scale.