Reputation: 333
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
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
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