在 Lisp 中附加到 "loop-collect" 的结果

Appending to the result of a "loop-collect" in Lisp

假设我运行以下

(loop for i to 4 collect i)

然后我得到一个列表 (0 1 2 3 4)。现在,如果我想在结果中附加一些东西,我可以在它的 last 元素上使用 rplacd,但是由于 Lisp 列表是链表,它不是很有效。这里的列表少得离谱,但这只是一个例子。

然而,由于循环工具 returns 列表以递增顺序排列,它必须跟踪指向最后一个元素的指针,并用 rplacd 或类似的东西更新结果。 macroexpand-all 表明这是 CCL 所做的,可能还有其他 lisps。

问题:有没有办法在finally子句中使用这个"pointer"?它允许在结果中附加一些东西,这有时很有用。

当然,编写指针机制很容易,但不是很好。例如,以下将列表 e 附加到列表 (0 1 ... n).

(defun foo (n e)
    (let* ((a (list nil)) (tail a))
        (loop for i to n
              do (rplacd tail (setf tail (list i)))
              finally (rplacd tail (setf tail e))
              (return (cdr a)))))

Question: Is there a way to use this "pointer" in the finally clause? It would allow one to append something to the result, which is sometimes useful.

我认为答案是"no"。 finally 子句的语法如下:

initial-final::= initially compound-form+ | finally compound-form+ 

当然,您可以将 收集到 一些特定变量,然后附加或 nconc :

CL-USER> (loop for i from 1 to 5
              collect i into ns
              finally (return (nconc ns (list 'a 'b 'c))))
;=> (1 2 3 4 5 A B C)

不过,这确实需要额外遍历结果,这可能是不希望的。 (我认为这是您要避免的。)

另一种选择是使用 nconc 而不是 collect 来构建列表。如果您正在使用 collect,那么您一次只是获取一个值,然后收集它。您可以将该值放入列表中,然后使用 nconc "collect" 它。如果将那个元素列表保存到循环中的一个变量中,就可以引用它,它几乎就是一个尾指针。例如:

CL-USER> (loop for i from 1 to 5
            for ilist = (list i)
            nconc ilist into ns
            finally (progn 
                      (nconc ilist '(a b c))  ; ***
                      (return ns)))
;=> (1 2 3 4 5 A B C)

在***标记的行中,调用nconc只需要遍历ilist的最终值即可,即(5)。这是一个非常快的 nconc 调用。

每次迭代的额外比较和额外的迭代给你这个:

CL-USER 2 > (defun foo (n e &aux (z (1+ n)))
              (loop for i to z
                    unless (= i z)
                      collect i
                    else
                      nconc e))
FOO

CL-USER 3 > (foo 4 '(f o o))
(0 1 2 3 4 F O O)