Michel
Michel

Reputation: 11755

Using &rest parameters in Common Lisp

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

Answers (1)

user5920214
user5920214

Reputation:

In this answer I'll start by explaining what the problem is, and then show two different approaches to solving it.

Understanding the problem

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.

The first approach: use 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.

The second approach: change 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

Related Questions