【问题标题】:Should I avoid resetting "by hand" an auto-incremented loop variable?我应该避免“手动”重置自动递增的循环变量吗?
【发布时间】:2020-04-22 03:08:57
【问题描述】:

我有一个包含几个变量的循环;其中一个在每一步递增。然而,有时这个变量可能会被重置为 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 标准?

【问题讨论】:

    标签: loops variables lisp common-lisp


    【解决方案1】:

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

    首先据我所知,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,不要因为代码中的一些隐蔽性而随机跳动。因此,我个人会出于风格而不是确保规范允许或不说它是安全的问题来避免这种情况。

    我宁愿按照 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 ...)... 求值 3 次,而 i 是适当的值,那么您就不能真正展开这样的循环。

    【讨论】:

    • 在样式问题上,我会在循环之外完全初始化z 变量,以明确z 不是循环迭代计数器。 (let ((z 0)) (loop ... do (incf z) ... (if ... (setf z 0))))with z = 0 也可以,但 for z from 1 肯定会让人们感到困惑。
    【解决方案2】:

    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))
    

    【讨论】:

    • 我认为您对此是正确的(这很可悲,因为它限制了 loop 可以优化的积极程度)。但我怀疑“可以通过评估表单来分配新值”可能是指for x = <form> then <form> 子句中的第二种形式。然而,loop 周围的任何措辞都不够清楚,无法确定我的想法。
    【解决方案3】:

    另一种可能性:

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

    【讨论】:

      猜你喜欢
      • 2020-02-10
      • 2018-10-02
      • 1970-01-01
      • 2017-04-26
      • 2012-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多