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