0

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.

Sam
  • 125
  • 7
  • 1
    Do you really want keywords? Your example input doesn't seem to be using them. – npostavs May 15 '15 at 16:15
  • @npostavs not really, initially I attempted to use &optional instead, but my reading suggested that &key should be used in combination with &rest instead, and I couldn't find any examples of &rest and &optional being used in combination as I wish to here. – Sam May 15 '15 at 16:17
  • 1
    Actually, I don't understand why you need anything beyond `&rest`. – npostavs May 15 '15 at 16:19
  • @npostavs because &rest throws an error when I try to feed it lists as arguments, quoted or otherwise. – Sam May 15 '15 at 16:23
  • Note that argument lists are not of arbitrary length. Check the value of `call-arguments-limit` in your Lisp. – Rainer Joswig May 15 '15 at 18:51

3 Answers3

1

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.

Sam
  • 125
  • 7
sds
  • 58,617
  • 29
  • 161
  • 278
  • Thanks for the solution. Is it a fools errand to try and break out the components going into &rest from the start like I was attempting, or can that be done? – Sam May 15 '15 at 16:47
  • I am not sure what you mean, but please see the edit – sds May 15 '15 at 16:52
  • The edit clarifies the use of &key for me, thanks! What I am trying to ask I suppose, is this - Can &rest can be properly followed by anything other than a single argument to contain the resulting list, and if so, what is there a simple use case example? – Sam May 15 '15 at 16:57
  • 1
    I suggest that you peruse [Ordinary Lambda Lists](http://www.lispworks.com/documentation/HyperSpec/Body/03_da.htm) ask a separate question if anything is unclear. – sds May 15 '15 at 17:04
1

I don't see a productive way to mix &rest and &key for solving this problem. Here's an example using only &key:

 (defun divisible-by (numerator &key divisors not-divisors)
   (flet ((dividesp (denom) (= 0 (mod numerator denom))))
     (and (every #'dividesp divisors)
          (notany #'dividesp not-divisors))))

 ;; Call like:
 (divisible-by 10 :divisors (list 2 5) :not-divisors (list 4 6))
 => t

Your lambda list has a syntax error, something close to what you wrote but valid would look and be called like this:

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

;; Calling would look like this
(divisible-by 10 :divisors (list 11 2) :divisorp nil)

-> (:DIVISORS (11 2) :DIVISORP NIL) 
-> (11 2) 
-> NIL 

Your desired input is not exactly possible for a function. Function calls don't alter the syntax of their arguments: (5 t) would be the function 5 called with the argument t, but 5 isn't a function so you get an error regardless of the lambda list.

m-n
  • 1,476
  • 1
  • 9
  • 9
0

Defun takes an ordinary lambda list. There is no destructuring in that.

&Rest takes exactly one symbol, which is bound to the rest of the arguments after all required and optional parameters are filled. If you want to destructure it, use destructuring-bind inside the function body.

Sometimes it may be worthwhile to use a macro, which takes a destructuring lambda list, to preprocess a function calling form.

Svante
  • 50,694
  • 11
  • 78
  • 122