Home > database >  Loop macro: How to collect local variables into several lists simultaneously?
Loop macro: How to collect local variables into several lists simultaneously?

Time:10-09

Suppose I have a function that returns two values inside a loop macro. Is there an elegant way to collect the first values into one list and second values into another list?

As a silly example, consider the following function:

(defun name-and-phone (id)
  (case id
    (0 (values "Peter" 1234))
    (1 (values "Alice" 5678))
    (2 (values "Bobby" 8910))))

Now I would like to collect the names into one list and phone numbers into another one. I could do something like

(loop for i upto 2
      collect (nth-value 0 (name-and-phone i))
        into names
      collect (nth-value 1 (name-and-phone i))
        into phones
      finally (return (values names phones)))

but that means I have to call name-and-phone twice for each i, which seems inefficient.

I could also use a temporary dotted list

(loop for i upto 2
      collect (multiple-value-bind (name phone)
                  (name-and-phone i)
                (cons name phone))
        into names-and-phones
      finally (return (values (mapcar #'car names-and-phones)
                              (mapcar #'cdr names-and-phones))))

but that does not feel very elegant.

Is there a way I could use loop's collect inside the scope of multiple-value-bind?

Googling around I could not find much; the closest is this question, where OP is collecting a variable of loop (and not from a nested scope).

CodePudding user response:

Use Destructuring:

(loop for n from 0 to 10
      for (f r) = (multiple-value-list (floor n 3))
      collect f into fs
      collect r into rs
      finally (return (values fs rs)))
==> (0 0 0 1 1 1 2 2 2 3 3)
    (0 1 2 0 1 2 0 1 2 0 1)

A sufficiently smart compiler should be able to avoid consing up a list in multiple-value-list.

CodePudding user response:

(loop with name and number
      for i from 0 upto 2
      do (setf (values name number)
               (name-and-phone i))
      collect name into names
      collect number into numbers
      finally (return (values names numbers)))

Alternative: there is the slightly more powerful ITERATE macro as a library.

  • Related