我应该避免重置 "by hand" 一个自动递增的循环变量吗?
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 标准?
另一种可能性:
(loop for z = 0 then (if ... 0 (1+ z))
...
)
我认为你应该避免这样做,原因有二。
首先据我所知loop
规范没有明确说明这是否安全,但是dotimes
例如说
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.
换句话说
(dotimes (i n)
... my code ...)
可能会扩展为类似
(let (...)
...
(tagbody
start
(let ((i ...))
...my code...)
(unless ... (go start))
...))
例如。
我非常怀疑 loop
实现在实践中不会这样做,我想你可能会争辩说规范暗示他们不允许这样做,但我也认为措辞不够清楚,你想依赖它。
其次我认为从风格上来说,在迭代主体中调整迭代变量的值是非常可怕的:如果有人阅读 (loop for i from 0 below 100 do ...)
他们期望我按从 0 到 99 的整数步进,而不是因为代码中的一些不明确的晦涩而随机跳跃。所以我个人会从风格上避免这种情况,而不是为了确保规范允许或不说它是安全的。
我宁愿按照 Rainer 的建议去做,也就是 (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
是合适的值,那么您就无法真正展开这样的循环。
ANSI CL 包含多个措辞实例,这些措辞具有明确的解释,即变量绑定一次并破坏性地步进。
就改变 "variable initialization and stepping clauses goes" 的变量而言,这是符合规范的代码可以做到的。 ANSL CL 标准明确了变量绑定一次然后通过赋值步进,并且子句按顺序处理,只有某些例外。
特别是6.1.2.1迭代控制中有这段文字:
for 和 as 子句通过使用一个或多个初始化为某个值并且可以在每次迭代后修改或步进的局部循环变量进行迭代。对于这些子句,当局部变量达到某个提供的值或当某个其他循环子句终止迭代时,迭代终止。在每次迭代中,变量可以递增或递减,或者可以通过对表单的评估来分配新值。
见最后一位:"can be assigned a new value by the evaluation of a form".
您使用 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))
我有一个包含多个变量的循环;其中之一在每一步递增。但是有时,这个变量可能会重置为 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 for z = 0 then (if ... 0 (1+ z))
...
)
我认为你应该避免这样做,原因有二。
首先据我所知loop
规范没有明确说明这是否安全,但是dotimes
例如说
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.
换句话说
(dotimes (i n)
... my code ...)
可能会扩展为类似
(let (...)
...
(tagbody
start
(let ((i ...))
...my code...)
(unless ... (go start))
...))
例如。
我非常怀疑 loop
实现在实践中不会这样做,我想你可能会争辩说规范暗示他们不允许这样做,但我也认为措辞不够清楚,你想依赖它。
其次我认为从风格上来说,在迭代主体中调整迭代变量的值是非常可怕的:如果有人阅读 (loop for i from 0 below 100 do ...)
他们期望我按从 0 到 99 的整数步进,而不是因为代码中的一些不明确的晦涩而随机跳跃。所以我个人会从风格上避免这种情况,而不是为了确保规范允许或不说它是安全的。
我宁愿按照 Rainer 的建议去做,也就是 (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
是合适的值,那么您就无法真正展开这样的循环。
ANSI CL 包含多个措辞实例,这些措辞具有明确的解释,即变量绑定一次并破坏性地步进。
就改变 "variable initialization and stepping clauses goes" 的变量而言,这是符合规范的代码可以做到的。 ANSL CL 标准明确了变量绑定一次然后通过赋值步进,并且子句按顺序处理,只有某些例外。
特别是6.1.2.1迭代控制中有这段文字:
for 和 as 子句通过使用一个或多个初始化为某个值并且可以在每次迭代后修改或步进的局部循环变量进行迭代。对于这些子句,当局部变量达到某个提供的值或当某个其他循环子句终止迭代时,迭代终止。在每次迭代中,变量可以递增或递减,或者可以通过对表单的评估来分配新值。
见最后一位:"can be assigned a new value by the evaluation of a form".
您使用 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))