我应该避免重置"by hand"自动递增的循环变量吗?



我有一个包含多个变量的循环;其中一个变量在每一步递增。但是,有时此变量可能会重置为 0。因此,我可以写:

(loop
with z = 0
...
do (progn
(setq (z (1+ z)))
...
(if ...
(setq z 0))))

但看起来我也可以写:

(loop
for z from 1
...
do (progn
...
(if ...
(setq z 0))))

第二个版本更短(一行(;但我不确定它是否真的干净;它是否完全符合Lisp标准?

我认为你应该避免这样做,原因有两个。

首先,据我所知,loop规范没有明确说明这是否安全,但dotimes例如说

dotimes是否在每次迭代上建立 var 的新绑定,或者是否在开始时为 var 建立一次绑定,然后在任何后续迭代中分配它,都取决于实现。

换句话说,

(dotimes (i n)
... my code ...)

可能会扩展到类似

(let (...)
...
(tagbody
start
(let ((i ...))
...my code...)
(unless ... (go start))
...))

例如。

我非常强烈地怀疑loop实现在实践中不会这样做,我认为您可能会争辩说该规范暗示他们不允许这样做,但我也认为措辞不够清晰,以至于您想要依赖它。

其次,我认为在迭代主体中调整迭代变量的值的风格问题是可怕的:如果有人阅读(loop for i from 0 below 100 do ...)他们希望 i 从 0 到 99 逐步执行整数,而不是因为代码中的一些狡猾的晦涩而随机跳跃。 因此,我个人会避免这样做,因为这是风格问题,而不是确保规范允许或不允许说它是安全的。

相反,我会按照莱纳的建议去做,这是(loop for x = ... then ...)很明显,x的值是由您的代码确定的,而不是由loop决定的。

最后请注意,您可以通过以下方式检测每次迭代时绑定的迭代变量,例如:

> (mapcar #'funcall (loop for i below 3
collect (lambda () i)))
(3 3 3)

在这种情况下,我正在使用的实现不会,在这种情况下会重新绑定它(但当然,在其他情况下可能会这样做!


还值得注意的是,分配给循环控制变量的可能性阻止了重要的优化:循环展开。 如果系统不能假设(loop for i from 0 to 2 do ...)计算...三次,i是适当的值,你就无法真正展开这样的循环。

另一种可能性:

(loop for z = 0 then (if ... 0 (1+ z))
...
)

ANSI CL 包含多个措辞实例,这些实例清楚地解释变量被绑定一次并破坏性地步进。

至于改变"变量初始化和步进子句"的变量,这是符合代码可以做的事情。ANSL CL 标准明确规定,变量绑定一次,然后通过赋值逐步绑定,并且子句按顺序处理,只有某些例外。

特别是,在 6.1.2.1 迭代控件中有这样的文本:

for 和 as 子句使用一个或多个局部循环变量进行迭代,这些变量初始化为某个值,并且可以在每次迭代后进行修改或单步执行。对于这些子句,当局部变量达到某个提供的值或某个其他循环子句终止迭代时,迭代终止。在每次迭代中,变量可以递增或递减,也可以通过表单的评估来分配新值。

请参阅最后一点:">可以通过评估表单来分配新值"。

使用with的替代代码在两个方面有点冗长。递增变量可以使用incf来完成。其次,loopdo条款有多种形式;无需progn.

因此,如果我们需要这个替代方案,我们至少可以这样:

(loop
with z = 0
...
do (incf z)
...
(when ...
(setq z 0)))

或者可能带有when条款:

(loop
with z = 0
...
do (incf z)
...
when (condition ...) do
(setq z 0))

最新更新