Read Chapter 20.
You already saw one way of defining a function so that it can be
called with different numbers of variables. That was by using the
&optional keyword, as described in the notes for
Chapter 17. The difference between
using the &optional keyword and the
&rest keyword, as described in Chapter 20, is that
the optional parameters are each bound to one optional argument, but the
rest parameter is bound to a list of an arbitrary number of optional
arguments. You can have both optional parameters and rest parameters,
as long as the &optional keyword comes before the
&rest keyword. If so, optional parameters are bound
to extra arguments as long as there are enough optional parameters,
and then the rest parameter is bound to a list of the rest. Here's a
demonstration:
TEST(114): (defun foo (x &optional y z &rest w)
(list x y z w))
FOO
TEST(115): (foo 1)
(1 NIL NIL NIL)
TEST(116): (foo 1 2)
(1 2 NIL NIL)
TEST(117): (foo 1 2 3)
(1 2 3 NIL)
TEST(118): (foo 1 2 3 4)
(1 2 3 (4))
TEST(119): (foo 1 2 3 4 5)
(1 2 3 (4 5))
Notice that if there is no explicit default value for optional
parameters, there is an implicit default value of NIL.
One problem with the example on pages 150 - 151 is that <= is
a special case in that its arguments are numbers, and numbers evaluate
to themselves. There is a more to deal with if we want to use this
technique with functions whose arguments don't evaluate to
themselves. To illustrate this, let's define a version of
equal that takes an arbitrary number of arguments.
Recall that = takes an arbitrary number of
arguments, but equal doesn't. To see this, have Lisp try
to evaluate
(= 5 (+ 2 3) (- 7 2))
and
(equal 5 (+ 2 3) (- 7 2))
So let's shadow equal in the ch20 package,
and try to define our own ch20::equal. We can make it
take at least two arguments:
(defpackage ch20
(:shadow equal))
(in-package ch20)
(defun equal (object1 object2 &rest more-objects)
"Returns True if all the objects are lisp:equal;
False, otherwise."
_____
)
To start filling in the blank, let's use lisp:equal as
the two-argument version of equal:
(defun equal (object1 object2 &rest more-objects)
"Returns True if all the objects are lisp:equal;
False, otherwise."
(if (lisp:equal object1 object2)
(if more-objects
_____
t)
nil))
(Recall that NIL is interpreted as False, and anything
else as True, so we can use more-objects instead of
(not (null more-objects)).
You can actually test this version of equal, as long as
you don't try to evaluate the blank. Try it.
Now, if object1 and object2 are equal,
and there are more objects, we want to return True if and only if
object2 is equal to all the objects in
more-objects. To do this, we want to evaluate a form
looking like
(equal object2 object3 ...)
We might think that we could get that form by evaluating
(cons 'equal (cons 'object2 more-objects))
which is the same as
`(equal object2 ,@more-objects)
So let's try
CH20(108): (defun equal (object1 object2 &rest more-objects)
"Returns True if all the objects are lisp:equal;
False, otherwise."
(if (lisp:equal object1 object2)
(if more-objects
(eval `(equal object2 ,@more-objects))
t)
nil))
CH20(109): (equal 5 (+ 2 3) (- 7 2))
Error: Attempt to take the value of the unbound variable `OBJECT2'.
[condition type: UNBOUND-VARIABLE]
The problem is that eval does not recognize the bindings
lambda variables get when their functions are called. We will see the
correct way to get around this in Chapter 22. Meanwhile, we have to
create a form which is the value of object2, but quoted.
Here is this version:
CH20(110): (defun equal (object1 object2 &rest more-objects)
"Returns True if all the objects are lisp:equal;
False, otherwise."
(if (lisp:equal object1 object2)
(if more-objects
(eval `(equal ',object2 ,@more-objects))
t)
nil))
CH20(111): (equal 5 (+ 2 3) (- 7 2))
T
That looks good, but we're still being fooled by the fact that numbers
evaluate to themselves. Let's try
CH20(112): (equal 'a 'a 'c 'd)
Error: Attempt to take the value of the unbound variable `C'.
The problem is that eval is now trying to evaluate the
form (EQUAL 'A C D) because the value of
more-objects is the list (C D). We need to
quote every form in the list more-objects. To help us
do this, let's define a function that is given a list, and returns the
same list with every form in it quoted:
(defun quotem (list)
"Returns the LIST with every form in it quoted."
(if (null list) '()
(cons (list 'quote (first list))
(quotem (rest list)))))
Try that.
Finally, here's our final version of equal:
(defun equal (object1 object2 &rest more-objects)
"Returns True if all the objects are lisp:equal;
False, otherwise."
(if (lisp:equal object1 object2)
(if more-objects
(eval `(equal ',object2 ,@(quotem more-objects)))
t)
nil))
and here's some tests:
CH20(121): (equal 'a 'a 'c 'd)
NIL
CH20(122): (equal 'a 'a 'a 'a)
T
CH20(123): (equal 'a (first '(a b c)) (second '(b a c)) (third '(c b a)))
T