Search code examples
common-lisp

conditional :for after a :when (in loop macro)


I would like to bind a variable inside a LOOP macro, but only conditionally.

Example:

(loop :for (num div) :in '((1 2) (4 2) (3 0) (1 4))
      :when (/= 0 div)
      :for res = (/ num div)
      :collect num
      :do (format T "~A divided by ~A = ~A~%" num div res))

This doesn't work as written:

:FOR does not introduce a LOOP clause that can follow WHEN.
current LOOP context: :FOR RES.
   [Condition of type SB-INT:SIMPLE-PROGRAM-ERROR]

Is there a way to do this inside a single loop call? Any solutions I can think of, involve breaking out of the loop somehow which has considerable drawbacks. Among others you lose access to the loop context (:collect etc).


Solution

  • You can't do that with loop. You can work around it as below if you have to use loop, although coredump's answer is better since a variable binding that is used exactly once might as well not exist.

    (loop for (num div) in '((1 2) (4 2) (3 0) (1 4))
          for q? = (and (not (zerop div)) (/ num div))
          when q?
            collect num
            and do (format T "~A divided by ~A = ~A~%" num div q?))
    

    However you can also just write Lisp rather than loop's fragile pseudo-fortran. The following uses Tim Bradshaw's collecting macro to factor value collection out of a looping construct (it also collects the quotient rather than numerator, so that there is some purpose to the binding):

    (collecting
      (dolist (v '((1 2) (4 2) (3 0) (1 4)))
        (destructuring-bind (numerator denominator) v
          (unless (zerop denominator)
            (let ((quotient (/ numerator denominator)))
              (collect quotient)
              (format T "~A divided by ~A = ~A~%" numerator denominator quotient))))))
    

    If the combination of iteration and destructuring is something you do a lot, then (using, this time, metatronic macros to make things a little easier):

    (defmacro/m destructuring-dolist ((ll list &optional (value 'nil)) &body forms)
      `(dolist (<v> ,list ,value)
         (destructuring-bind ,ll <v>
           ,@forms)))
    

    And now

    (collecting
      (destructuring-dolist ((numerator denominator) '((1 2) (4 2) (3 0) (1 4)))
        (unless (zerop denominator)
          (let ((quotient (/ numerator denominator)))
            (collect quotient)
            (format T "~A divided by ~A = ~A~%" numerator denominator quotient)))))
    

    If you want the numerators and the quotients, well:

    (with-collectors (numerator quotient)
      (destructuring-dolist ((numerator denominator) '((1 2) (4 2) (3 0) (1 4)))
        (unless (zerop denominator)
          (let ((quotient (/ numerator denominator)))
            (quotient quotient)
            (numerator numerator)
            (format T "~A divided by ~A = ~A~%" numerator denominator quotient)))))
    

    And of course, if you want to, now you can rely on the fact that you actually have fully-fledged destructuring lambda lists rather than whatever loop supports:

    (with-collectors (numerator quotient)
      (destructuring-dolist ((numerator denominator &aux
                                        (valid (not (zerop denominator)))
                                        (quotient (when valid (/ numerator denominator))))
                             '((1 2) (4 2) (3 0) (1 4)))
        (when valid
          (quotient quotient)
          (numerator numerator)
          (format T "~A divided by ~A = ~A~%" numerator denominator quotient))))