英文:
Should I avoid resetting "by hand" an auto-incremented loop variable?
问题
以下是您要翻译的内容:
"我有一个包含多个变量的循环;其中一个变量在每一步都会递增。然而,有时这个变量可能会被重置为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标准?"
英文:
I have a loop containing several variables; one of them is incremented at each step. Sometimes however, this variable may be reset to 0. Thus I can write:
(loop
with z = 0
...
do (progn
(setq (z (1+ z)))
...
(if ...
(setq z 0))))
but it looks I can also write:
(loop
for z from 1
...
do (progn
...
(if ...
(setq z 0))))
The second version is shorter (by one line); but I am not sure it is really clean; is it fully compliant with Lisp standard?
答案1
得分: 6
Another possibility:
(循环,当 z = 0 时,则 z = 0,否则 z = z + 1)
(...)
英文:
Another possibility:
(loop for z = 0 then (if ... 0 (1+ z))
...
)
答案2
得分: 6
I think you should avoid doing so, for two reasons.
首先,就我所看到的loop
规范没有明确说明这是否安全,但是例如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逐步增加,而不是因为代码中的某些隐晦而随机跳跃。因此,我个人会避免这种做法,而不是因为规范是否允许或未允许而这样做。
相反,我会像Rainer建议的那样,使用(loop for x = ... then ...)
,在这种情况下,清楚地表示x
的值是由你的代码确定的,而不是由loop
确定的。
最后,值得注意的是,对循环控制变量进行赋值的可能性阻止了一种重要的优化:循环展开。如果系统不能假设(loop for i from 0 to 2 do ...)
会三次评估...
,并且i
是适当的值,那么你不能真正展开这样的循环。
英文:
I think you should avoid doing so, for two reasons.
Firstly as far as I can see the loop
specification makes no clear statement about whether this is safe, but dotimes
for instance says that
> It is implementation-dependent whether dotimes
establishes a new binding of var on each iteration or whether it establishes a binding for var once at the beginning and then assigns it on any subsequent iterations.
In other words
(dotimes (i n)
... my code ...)
might expand to something like
(let (...)
...
(tagbody
start
(let ((i ...))
...my code...)
(unless ... (go start))
...))
for instance.
I suspect pretty strongly that loop
implementations don't do this in practice, and I think you probably could argue that the spec implies they are not allowed to do it, but I also think that the wording is not clear enough that you'd want to depend on that.
Secondly I think that as a matter of style adjusting the iteration variable's value inside the body of the iteration is, well, horrible: If someone reads (loop for i from 0 below 100 do ...)
they expect i to step by integers from 0 to 99, not to jump about randomly because of some devious obscurity in the code. So I would personally avoid this as a matter of style rather than as a matter of being sure the spec allows or does not say it is safe.
Rather I would do what Rainer suggested, which is (loop for x = ... then ...)
where it is clear that x
has values which are determined by your code, not by loop
.
Finally note that you can detect the iteration-variable-bound-on-each-iteration thing by, for instance:
> (mapcar #'funcall (loop for i below 3
collect (lambda () i)))
(3 3 3)
In this case the implementation I am using does not, in this case rebind it (but it might do in other cases of course!).
It's also worth noting that the possibility of assignment to loop control variables prevents an important optimisation being possible: loop unrolling. If the system can't assume that (loop for i from 0 to 2 do ...)
evaluates ...
three times, with i
being the appropriate value, you can't really unroll such a loop.
答案3
得分: 3
ANSI CL包含多个措辞实际上明确解释了变量只绑定一次并进行破坏性步进。
就"变量初始化和步进子句的变量"的变异而言,符合规范的代码可以执行此操作。ANSI CL标准明确规定变量只绑定一次,然后通过赋值进行步进,并且子句按顺序处理,只有某些例外情况。
特别是,在6.1.2.1 迭代控制中有这样的文本:
for和as子句通过使用一个或多个本地循环变量进行迭代,这些变量被初始化为某个值,并且可以在每次迭代后进行修改或步进。对于这些子句,迭代在本地变量达到某个提供的值或某个其他循环子句终止迭代时终止。在每次迭代中,变量可以通过表达式的评估进行步进,也可以分配一个新值。
看最后一句:“可以通过评估表达式来分配新值”。
您使用with
的替代代码有两个方面有点冗长。增加变量可以使用incf
完成。其次,loop
的do
子句接受多个表达式;不需要使用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))
英文:
ANSI CL contains multiple instances of wording which have the clear interpretation that variables are bound once and stepped destructively.
As far as mutating the variables of "variable initialization and stepping clauses goes", this is something that conforming code can do. The ANSL CL standard makes it clear that variables are bound once and then stepped by assignment, and that clauses are processed in order, with only certain exceptions.
In particular, there is this text in 6.1.2.1 Iteration Control:
The for and as clauses iterate by using one or more local loop variables that are initialized to some value and that can be modified or stepped after each iteration. For these clauses, iteration terminates when a local variable reaches some supplied value or when some other loop clause terminates iteration. At each iteration, variables can be stepped by an increment or a decrement or can be assigned a new value by the evaluation of a form.
See the last bit: "can be assigned a new value by the evaluation of a form".
Your alternative code using with
is a bit verbose on two counts. Incrementing a variable can be done with incf
. Secondly, the do
clause of loop
takes multiple forms; no need for progn
.
Thus, if we needed this alternative, we could at least have it like this:
(loop
with z = 0
...
do (incf z)
...
(when ...
(setq z 0)))
Or possibly with a when
clause:
(loop
with z = 0
...
do (incf z)
...
when (condition ...) do
(setq z 0))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论