3

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.

1
  • It works, thank you ! :D I'm ashamed, I counfounded funcall and apply and forgot apply took a list as argument. Solved. <3 Commented Oct 13, 2018 at 21:57

2 Answers 2

5

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!

Sign up to request clarification or add additional context in comments.

Comments

3

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.