Search code examples
argumentslispcommon-lispsbcl

Common Lisp - Giving &rest Lists as Arguments


As a result of musings around an exercism problem, I am trying to write a function that takes an input number and an arbitrary length list of divisors to test, along with the expected divisibility (i.e. remainder 0) as a boolean, returning true if all expectations are met (defaulting to true if unspecified).

example input:

 (divisible-by 10 (5 t) (4 f) 2) => t

My reading has lead to this attempt at creating the input for the function:

(defun divisible-by (numerator &rest args (&key divisors (divisorp t)))
  (loop...))

My simple test cases for such an input type error out in various ways, and my searching via Google and directly here on Stack Overflow have not proved fruitful, leading me to believe my understanding is insufficient to generate the right keywords.

Pointers on how to implement such a function, where my attempts fall down or why such a function cannot be implemented as I have outlined would be gratefully received.


Solution

  • You don't need anything beyond &rest:

    (defun divisible-p (number &rest divisors)
      "Check whether number is divisible by divisors."
      (dolist (spec divisors t) ; For each 'divisor' in divisors
        (etypecase spec
          ;; If divisor is a list, test if modulus of first value is 0
          ;; then compare to second (boolean)
          (cons (unless (eq (zerop (mod number (first spec)))
                            (second spec))
                  (return nil)))
          ;; If divisor is an integer, return t if modulus == 0
          (integer (unless (zerop (mod number spec))
                     (return nil))))))
    
    (divisible-p 10 '(5 t) '(4 nil) 2)
    ==> T
    (divisible-p 10 '(5 t) '(4 nil) 2 3)
    ==> NIL
    

    Note that you need to quote list arguments, otherwise you will get an error that there is no function 5.

    I am not sure what you were trying to accomplish by using &key, but they cannot be repeated in CL, i.e., if you write

    (defun foo (&key a) (print a))
    (foo :a 1 :a 2 :a 3)
    

    only 3 will be printed, 1 & 2 will be ignored.