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.