Search code examples
argumentscommon-lispapplyfunction-callparameter-list

common lisp invoking function without apply leads to odd number of keywords


i am currently learning common lisp and stumbled upon a question i could not answer my self:

(defun find-all (item seq &rest keyword-args &key (test #'eql)
                     test-not &allow-other-keys)
 (if test-not
   (apply #'remove item seq
          :test-not (complement test-not) keyword-args)
   (apply #'remove item seq
          :test (complement test) keyword-args)))

The function is used to find every element in seq matching item according to a test function. Sadly I dont understand why the function 'apply' was used here. Shouldn't it be possible to just invoke remove without apply? A warnings says: "The function has an odd number of arguments in the keyword portion" if I invoke remove without apply.

I hope you can help me, Thanks in advance!


Solution

  • REMOVE and FIND-ALL

    REMOVE takes a bunch of keyword arguments: from-end, test, test-not, start, end, count and key.

    Now in the function FIND-ALL you want to modify just one: either test or test-not and then call REMOVE.

    How to write the parameter list of FIND-ALL

    Now you have basically three options to write the parameter list of FIND-ALL, since it is basically the same as REMOVE with only one argument changed.

    1. list every keyword argument with their defaults and later pass them on with the necessary changed to REMOVE.

    2. list only a rest arguments list, manipulate this argument list and pass the new one via APPLY to REMOVE.

    3. mix of 1. and 2. like in the example above. List only the required arguments and the keyword arguments to modify plus a rest list of the other keyword arguments provided at call time. Call REMOVE via APPLY.

    How good are the three possibilities?

    Now 1. has the advantage that you get to see a full parameter list for FIND-ALL and that it does not need to cons an argument list. Lisp can check some of that. But you really need to copy all arguments in your parameter list and later in the calls to REMOVE. Possible but not that great.

    Then 2. has the disadvantage of not having a visible parameter list for FIND-ALL, but it might be easier to write with a few functions to manipulate argument lists. 3. is relatively easy to write, but also lacks the full parameter list.

    Your example

    Thus in your example it is version 3 of above:

    • the required arguments are passed in first
    • next are the modified arguments
    • last is the list of all keyword arguments

    If you want to pass an existing list of arguments as part of the arguments to the function, then you need APPLY. That's why it is used there. APPLY calls a function with the arguments taken from a list.

    CL-USER 1 > (apply #'+ 1 2 '(3 4 5))
    15
    
    CL-USER 2 > (let ((numbers '(3 4 5)))
                  (apply #'+ 1 2 numbers))
    15
    
    
    CL-USER 3 > (+ 1 2 3 4 5)
    15