Reputation: 11755
I am having some trouble using &rest parameters in a lisp program (with Common Lisp).
My knowledge of the syntax not being perfect, I may be doing something wrong.
I have two functions:
(defun func-one(&rest params) .....)
(defun func-two(param-a param-b &rest params) .....)
They are working but at some point, inside func-one I need to make a call to function-two, like this:
(func-two value-a value-b params)
I presume what I want is clear to a human reader, but my syntax is wrong because I get an error message like:
*** _ =: (1 2 3 4 5) is not a number
Since "&rest params" is meant to hold a list of numbers, I understand the message. But how can I get the result I want?
(1 2 3 4 5) passed in the "&rest params" should be seen as : 1 2 3 4 5 by func-two
and not as one element which is a list (indeed not a number).
So, what is the right way to call func-two? Instead of what I did.
Upvotes: 3
Views: 416
Reputation:
In this answer I'll start by explaining what the problem is, and then show two different approaches to solving it.
Start by looking at the two definitions:
(defun func-one (&rest params) ...)
So, func-one
is a function that takes any number of arguments (including zero). Inside its body all the arguments will be wrapped in a list and will be bound to params
: the length of params
will be the number of arguments provided to the function.
(defun func-two (param-a param-b &rest params) ...)
func-two
takes at least two arguments, and as many as you like (up to the implementation limit: this is true for func-one
as well). The first two arguments will be bound to param-a
and param-b
, and all the rest of the arguments will be wrapped in a list and bound to params
.
So we can write a little fake version of func-two
which explains what arguments it gets:
(defun func-two (param-1 param-2 &rest params)
(format t "~&~D arguments~%param-1: ~S~%param-2: ~S~%params: ~S~%"
(+ 2 (length params))
param-1 param-2 params)
(values))
And now we can call it in various ways:
> (func-two 1 2)
2 arguments
param-1: 1
param-2: 2
params: nil
> (func-two 1 2 3)
3 arguments
param-1: 1
param-2: 2
params: (3)
> (func-two 1 2 3 4)
4 arguments
param-1: 1
param-2: 2
params: (3 4)
> (func-two 1 2 '(3 4))
3 arguments
param-1: 1
param-2: 2
params: ((3 4))
And here's the problem: if you already have a bunch of things packed up into a list, then when you call func-two
, that list is only one argument to the function: they're not 'all the rest of the arguments' which is what you want.
There are two approaches to solving this problem, described in sections below.
apply
The first and the simplest is to use apply
whose purpose in life is to deal with this: if you want to call a function with a bunch of arguments you have in a list, as well as perhaps some leading arguments you have individually, you do so with apply
:
> (apply #'func-two 1 2 '(3 4))
4 arguments
param-1: 1
param-2: 2
params: (3 4)
> (apply #'func-two '(1 2 3 4))
4 arguments
param-1: 1
param-2: 2
params: (3 4)
You can see what apply
is doing: the first argument to it is the function to call, the last argument to it is 'all the rest of the arguments' and any arguments between are some leading arguments.
[The remainder of this answer talks about design, which is necessarily a matter of opinion.]
Using apply
is a perfectly decent technical answer (there are some issues about arglist length & performance but I think those are secondary here). However, it is very often the case that having to use apply
in this way is a symptom of some kind of design problem. To see why this is consider what your function definitions imply to the human reader.
(defun frobnicate (a b &rest things-to-process) ...)
What this means is that the frobnicate
function really has three arguments in its body (calling it binds three variables), where the third argument is some list of things to process. But at the user level I want to call it without having to specify that list explicitly, so I specify this third argument as &rest
which means the function actually takes two or more arguments from the caller's perspective.
But then consider this code fragment:
(apply #'frobnicate this that things-to-process)
What does this mean to someone reading it? Well, it means that you know exactly what function you want to call, frobnicate
, and you already have the three arguments that frobnicate
actually wants: whatever the leading two are, and then the list of things to process. But because of the way frobnicate
is defined you can't just pass them to it: instead you have to spread things-to-process
into a set of individual arguments with apply
and then immediately unspread it back into a (new) list in the process of calling frobnicate
. That smells bad.
In general if you end up having to use apply
to spread arguments to a function which you wrote, and whose name you know at the time of writing the code which calls it then this is often a sign of some kind of impedance-mismatch in the design.
So the second answer to the problem is to change the design so this impedence-mismatch does not happen. A way of solving impedance-mismatches like this is to define things in layers:
(defun frobnicate (a b &rest things-to-process)
;; user convenience function
(frob a b things-to-process))
(defun frob (a b things-to-process)
;; implementation
...)
And now, within the implementation where you've already got the list of objects to process, you call frob
rather than frobnicate
, and you no longer need to use apply
.
Upvotes: 8