循环宏:如何将局部变量同时收集到多个列表中?

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

假设我有一个函数 returns 在 loop 宏中有两个值。有没有一种优雅的方法可以将第一个值收集到一个列表中,将第二个值收集到另一个列表中?

作为一个愚蠢的例子,考虑以下函数:

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

现在我想将名字收集到一个列表中,将 phone 号码收集到另一个列表中。我可以做类似

的事情
(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)))

但这意味着我必须为每个 i 调用两次 name-and-phone,这似乎效率低下。

我也可以使用临时的虚线列表

(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))))

但是感觉不是很优雅

有没有办法在 multiple-value-bind 的范围内使用 loopcollect

谷歌搜索我找不到太多;最接近的是 ,其中 OP 正在收集 loop 的变量(而不是来自嵌套范围)。

使用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)

足够聪明的编译器应该能够避免在 multiple-value-list 中使用列表。

另见 values function in Common Lisp

(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)))

备选方案:有稍微更强大的 ITERATE 宏作为库。

loop 的一个问题是,虽然它非常擅长它所做的事情,但如果你想做它不会做的事情,你将立即拥有大量不-非常容易理解的代码。我想这就是你在这里看到的,其他答案和我认为的一样好:只是有点痛苦。

我认为减少痛苦的流行答案是使用可扩展的 loop,其中至少有一个,或者一些替代的综合迭代宏。

在比任何人都应该允许的情况下深入那些兔子洞后,我最近决定答案不是永远更花哨的迭代宏,而是将问题分解成,除其他外,更少花哨的宏会迭代,更不用说花哨的宏收集对象,然后可以自然地组合在一起。

所以在你的情况下你可以:

(with-collectors (name phone)
  (dotimes (i 2)
    (multiple-value-bind (n p) (name-and-phone i)
      (name n) (phone p))))

或者,因为自然地支持多个值是很有用的,你可以这样写:

(defmacro collecting-values ((&rest collectors) &body forms)
  (let ((varnames (mapcar (lambda (c)
                            (make-symbol (symbol-name c)))
                          collectors)))
    `(multiple-value-bind ,varnames (progn ,@forms)
       ,@(mapcar #'list collectors varnames))))

给你

(with-collectors (name phone)
  (dotimes (i 2)
    (collecting-values (name phone)
      (name-and-phone i))))

然后

> (with-collectors (name phone)
    (dotimes (i 2)
      (collecting-values (name phone)
        (name-and-phone i))))
("Peter" "Alice")
(1234 5678)

(collecting / with-collectors 是我一个朋友写的,可以找到here。)