循环宏:如何将局部变量同时收集到多个列表中?
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
的范围内使用 loop
的 collect
?
谷歌搜索我找不到太多;最接近的是 ,其中 OP 正在收集 loop
的变量(而不是来自嵌套范围)。
(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。)
假设我有一个函数 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
的范围内使用 loop
的 collect
?
谷歌搜索我找不到太多;最接近的是 loop
的变量(而不是来自嵌套范围)。
(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。)