4

I have the following Common Lisp Function:

(defun test(A &rest indexes)
  (if (null (first indexes))
      A
    (test (nth (+ 1 (first indexes)) A) (rest indexes))
  )
)

As far as I know &rest parameters are treated as a list in the function body but since

(rest indexes) also returns a list I'm stuck with nested Lists as parameters.

For example (test '("a" "b" "c" ("d" "e")) 3 1 6 7)

would cause indexes to be ((1 6 7)) at the second call.

Is there any way to pass my list without having this problem?

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346

3 Answers3

3

Basic style rule: don't use &rest arguments for list processing functions.

Why? Common Lisp implementations are allowed to only support up to the value of CALL-ARGUMENTS-LIMIT number of arguments. This number is 50 or larger, depending on implementation.

This means your function might in some implementation process lists not larger as fifty items.

Better: pass the list as a separate argument.

(defun test (A indexes)
   ...)

(test '("a" "b" "c" ("d" "e")) '(3 1 6 7))

Wrong solution: don't use apply, since it does not solve the problem of limited argument lists.

CLISP

[1]> call-arguments-limit
4096
[2]> (defun l1 (&rest l) l)
L1
[3]> (apply #'l1 (loop repeat 5000 collect 1))

*** - APPLY: too many arguments given to
      #<FUNCTION L1 (&REST L)
         (DECLARE (SYSTEM::IN-DEFUN L1))
         (BLOCK L1 L)>
The following restarts are available:
ABORT          :R1      Abort main loop
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
  • 1
    I agree with the recommendation, can you show how to code the function the right way? – Barmar Jan 04 '18 at 21:09
  • And yet, this is sometimes exactly what we want... for syntactic reasons, for example. In my current case it is something like `(fetch model &rest path)` which is more ergonomic to use (`(fetch *view-model* :attributes :source-color)` compared to `(fetch *view-model* '(:attributes :source-color))`. As such, I think a recipe for such cases is nice to have on this site. – BitTickler Dec 20 '22 at 22:39
3

rest is a accessor function that is paired together with first to give you the first element and the rest of the list. rest is the same as cdr.

&rest is a lambda list keyword that slurps the remaining arguments in the variable name that follows it.

You are really looking for apply. Imagine I make a function that can take 0 or more numeric parameters and add them together:

(defun add (&rest numbers)
  (apply #'+ numbers))

Apply can take more than two arguments. The first is the function to call and all but the last are extra arguments that are put in front of the last arguments element. You are guaranteed the implementation supports 50 arguments or upto the number of arguments a function can take in that particular implementation supports above 50.

(apply #'+ 1 2 '(3 4 5)) ; ==> 15

Now recursing by &rest and apply makes inefficient code so you should use higher order functions, loop macro, or make a helper:

;; higher order function
(defun fetch-indexes (a &rest indexes)
  (mapcar (lambda (i) (nth i a)) indexes))

;; loop macro
(defun fetch-indexes (a &rest indexes)
  (loop :for i :in indexes
        :collect (nth i a)))

;; helper function
(defun fetch-indexes (a &rest indexes)
  (labels ((helper (indexes)
             (if (endp indexes)
                 '()
                 (cons (nth (first indexes) a)
                       (helper (rest indexes))))))
    (helper indexes)))

;; test (works the same with all)
(fetch-indexes '(a b c d) 2 3 0 1)
; ==> (c d a b)

Using apply in recursion should be avoided, but I'll show how it's done.

(defun fetch-indexes (a &rest indexes)
  (if (endp indexes)
      '()
      (cons (nth (first indexes) a)
            (apply #'fetch-indexes a (rest indexes)))))

In your example you have nested lists. In order for that to work you would need to flatten it as well. I haven't done that so these supports one proper list of elements.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • To make this answer optimal, you should show how to use `apply` in his function. – Barmar Jan 04 '18 at 21:08
  • 1
    `Apply can take any number of arguments` -> not in Common Lisp. – Rainer Joswig Jan 04 '18 at 21:11
  • @RainerJoswig I actually didn't think of the limitation, more that it supported more than 2. In this case you are guaranteed that `apply` will work with `(rest indexes)` since both are required to support 50 and at least the call argument limit `CALL-ARGUMENTS-LIMIT`. – Sylwester Jan 04 '18 at 21:52
  • 'Imagine I make a function that can take any number of numbers and would add them together:' - you might want to make it clearer that ADD cannot take any number of numbers. – Rainer Joswig Jan 04 '18 at 22:04
  • @RainerJoswig The OP seems to want to pick a few elements from a structure so I doubt it will ever get close to the 50 argument guaranteed limit. The second you get passed that I guess you would want to pass the indexes in a structure and perhaps do a one pass and index the data making it a O(n) instead of O(n^2) which all my solutions are now. – Sylwester Jan 04 '18 at 22:19
  • `apply` will work here because the number of arguments is one less than it was in the outer call. But the whole thing sucks. –  Jan 08 '18 at 22:35
0

Another solution, albeit a bit more verbose, but less reliant on details of apply is to use a sub-function for the recursion, which just takes a list. The outer "main" entry point takes the &rest indices syntax, while the inner function takes it as a list:

(defun test (a &rest indices)
  (labels ((internal-test (indices)
              ;; ... the recursion thing calling internal-test
           ))
    (internal-test indices)))

In contrast to the (defun add (&rest args) ... ) example, you might still have some arguments before the rest, which in case of using apply would require you to append those to the variable arguments list.

Of course, as Rainer Joswig pointed out, you need to take care only to use this, when the number of variable args is small.

BitTickler
  • 10,905
  • 5
  • 32
  • 53