Fnifni
Fnifni

Reputation: 333

Function with rest arguments calling a function with rest arguments

Let us suppose we have a function func1 :

(defun func1 (&rest values)
  ; (do something with values...)
  (loop for i in values collect i))

Now, we have a function func2 which calls func1 :

(defun func2 (&rest values)
  ; (do something with values...)
  (func1 ???))

What should I put instead of ??? to "copy" all the parameters of func2's values to func1's values ?

For instance, I would have the following behavior :

(func2 1 2 3 4) ; result is (1 2 3 4) and not ((1 2 3 4)).

In an earlier question I tried to do something like this :

(defun func2 (&rest values)
  (macrolet ((my-macro (v)
               `(list ,@v)))
    (func1 (my-macro values))))

But the defun cannot get the value because it is not runtime. In this answer, he suggested that I use apply, but this function takes a &rest parameter too, so it doesn't solve my problem...

If possible, I would rather avoid to change the prototype of both functions, and the behavior of func1.

Upvotes: 3

Views: 803

Answers (2)

Rainer Joswig
Rainer Joswig

Reputation: 139401

lists vs. spread arguments

In Common Lisp it is good style to pass lists as lists and not as spread arguments:

(foo (list 1 2 3))   ; better interface

(foo 1 2 3)          ; interface is not so good

The language has been defined in a way that efficient function calling can be used by a compiler and this means that the number of arguments which can be passed to a function is limited. There is a standard variable which will tell us how many arguments a particular implementation supports:

This is LispWorks on my Mac:

CL-USER 13 > call-arguments-limit
2047

Some implementations allow much larger number of arguments. But this number can be as low as 50 - for example ABCL, Common Lisp on the JVM, allows only 50 arguments.

Computing with argument lists

But sometimes we want the arguments as a list and then we can use the &rest parameter:

(lambda (&rest args)
  (print args))

This is slightly in-efficient, since a list will be consed for the arguments. Usually Lisp tries to avoid to cons lists for arguments - they will be passed in registers or on the stack - if possible.

If we know that the argument list will not be used, then we can give the compiler a hint to use stack allocation - if possible:

(lambda (&rest args)
  (declare (dynamic-extent args))
  (reduce #'+ args))

In above function, the list of arguments can be deallocated when leaving the function - because the argument list is no longer used then.

If you want to pass these arguments to another function you can use FUNCALL and usually more useful APPLY:

(lambda (&rest args)
  (funcall #'write (first args) (second args) (third args)))

or more useful:

(lambda (&rest args)
  (apply #'write args))

One can also add additional arguments to APPLY before the list to apply:

CL-USER 19 > ((lambda (&rest args)
                (apply #'write
                       (first args)               ; the object
                       :case :downcase            ; additional args 
                       (rest args))
                (values))
              '(defun foo () 'bar)
              :pretty t
              :right-margin 15)
(defun foo ()
  'bar)

Upvotes: 3

Gwang-Jin Kim
Gwang-Jin Kim

Reputation: 10010

In common lisp, it has to be

(apply #'func1 values) ;; since `func1` has to be looked up in function namespace

remember, Clojure and Racket/Scheme are Lisp1, and common lisp is Lisp2.

Alternative solution (just for the sake)

I was asking myself, how to get it done without apply - just for the sake. The problem with

`(func2 ,@values)

is, that if e.g.

 (func2 (list 1 2 3) (list 4) 5)

is called, the values variable is ((1 2 3) (4) 5) But when it is spliced into (func1 ,@values), what is created is (func1 (1 2 3) (4) 5). But if we compare this with the func2 call, it should be rather (func1 (list 1 2 3) (list 4) 5) which is perhaps not possible, because when (func2 (list 1 2 3) (list 4) 5) is called - in the lisp manner - the arguments of func2 are each evaluated, before they enter the function body of func2, so we end up with values as a list of already evaluated arguments, namely ((1 2 3) (4) 5).

So somehow, concerning the arguments for func1 in the last expression, we are one evaluation-step offbeat.

But there is a solution with quote, that we manage to quote each of the arguments before giving it to func1 in the last expression, to "synchronize" the func1 function call - to let the arguments' evaluation pause for one round.

So my first aim was to generate a new values list inside the func2 body where each of the values list's argument is quoted (this is done in the let-binding). And then at the end to splice this quoted-values list into the last expression: (func1 '(1 2 3) '(4) '5) which can be regarded as equivalent to (func1 (list 1 2 3) (list 4) 5) for this kind of problems / for this kind of calls. This was achieved by this code:

(defun func2 (&rest vals)
  (let ((quoted-values (loop for x in vals
                                     collect `',x)))
    ; do sth with vals here - the func2 function -
    (eval `(func1 ,@quoted-values))))

This is kind of a macro (it creates code btw. it organizes new code) but executed and created in run-time - not in pre-compile time. Using an eval we execute that generated code on the fly.

And like macroexpand-1, we can look at the result - the code - to which the func1 expression "expands", by removing eval around it - I call it func2-1:

(defun func2-1 (&rest vals)
  (let ((quoted-values (loop for x in vals
                                     collect `',x)))
    ; do sth with vals here - the func2 function -
    `(func1 ,@quoted-values)))

And if we run it, it returns the last expression as code immediately before it is evluated in the func2 version:

(func2-1 (list 1 2 3) (list 4) 5)
;; (FUNC1 '(1 2 3) '(4) '5) ;; the returned code
;; the quoted arguments - like desired!

And this happens if we call it using func2 (so with evaluation of the func1 all:

(func2 (list 1 2 3) (list 4) 5) 
;; ((1 2 3) (4) 5)  ;; the result of (FUNC1 '(1 2 3) '(4) '5)

So I would say this is exactly what you desired!

Upvotes: 5

Related Questions