0

In many languages I have used, there is a way to have a variable number of arguments in the function signature, e.g. in Go you can declare a function with func doSomething(args ...int) and call it with doSomething(2, 3, 4) or in Python def do_something(*args) and do_something(1, 2, 3), etc. Additionally, there is some syntax for spreading/unpacking these args, e.g. in Go things = append(things, others...) or in Python do_something_else(*args).

I want to make a wrapper that takes a format string and passes it and any number of arguments to format t. Since format takes a variable number of arguments, my guess is that this is a language feature, yet I have not been able to find any explanation of how to do this.

The code I have that does not work correctly looks like this:

(defun printf (fstr vals)
  (format t (concatenate 'string fstr "~%") vals))

If I call it with a format string that requires 1 value, it works fine, but I have no idea how to make it work with more than that. For example, (printf "~d is not ~a" 12 "13") results in the error "too many arguments given to printf". I also tried the following:

(defun printf (fstr &rest vals)
  (format t (concatenate 'string fstr "~%") vals))

The error then says "there are not enough arguments left for this format directive". It is clear that vals is a list of arguments, but I have no idea how to expand them to pass to the call to format t.

Am I accepting args correctly by using the &rest notation? How do I expand that list of values in the call to format t?

Jonathan Voss
  • 1,126
  • 1
  • 9
  • 24

2 Answers2

5

Your example

(defun printf (fstr &rest vals)
  (apply #'format t (concatenate 'string fstr "~%") vals))

&rest declares the vals parameter to take a list of the remaining arguments.

Example of &rest and apply

CL-USER 36 > (defun example (a b &rest c) (values a b c))
EXAMPLE

CL-USER 37 > (example 1 2 3 4 5 6 7 8)
1
2
(3 4 5 6 7 8)

Above one can see that c is bound to the list (3 4 5 6 7 8). a and b are bound to the arguments 1 and 2.

apply calls a function with arguments taken from a list. The last argument of apply needs to be a list.

CL-USER 38 > (apply #'+ 1 2 '(3 4 5 6 7 8))
36

Now we can use apply in our example function:

CL-USER 39 > (defun example (a b &rest c)
               (apply #'+ a b c))
EXAMPLE

CL-USER 40 > (example 1 2 3 4 5 6 7 8)
36
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • I read somewhere after posting this question that the single quote syntax passes something as a symbol rather than as a value, but I'm not sure what the hashtag does. Can you add a quick note about that specific syntax? – Jonathan Voss Jul 26 '23 at 14:26
  • @JonathanVoss: it retrieves the function object. – Rainer Joswig Jul 26 '23 at 16:56
  • @JonathanVoss -- `#'+` is an equivalent shorthand for [`(function +)`](http://www.lispworks.com/documentation/HyperSpec/Body/s_fn.htm). – ad absurdum Jul 27 '23 at 13:42
1

Found this answer after posting. (Did not know the keywords "unroll" or "splat" were relevant here.) The answer seems to be to use apply.

(defun printf (fstr &rest vals)
  (apply #'format t (concatenate 'string fstr "~%") vals))

I do not understand how/why it works, but it does. Somehow, this is syntactically equivalent to the spread operators in JavaScript, Python, and Go.

Jonathan Voss
  • 1,126
  • 1
  • 9
  • 24
  • unroll/splat were used by the person asking the question, but asking about how to call a function on a list of arguments or how to use `&rest` arguments is how the questions would have been fine too: https://stackoverflow.com/questions/42219300/how-to-deal-with-rest-args-in-common-lisp/42228285#42228285. – coredump Jul 25 '23 at 22:53
  • 1
    APPLY and FUNCALL are fundamental functions that you really have to understand if you wish to use Common Lisp. http://clhs.lisp.se/Body/f_apply.htm See also : https://stackoverflow.com/questions/3862394/when-do-you-use-apply-and-when-funcall – Jérôme Radix Jul 26 '23 at 14:37