【问题标题】:for/continue in scheme/lispfor/continue in scheme/lisp
【发布时间】:2011-01-03 14:06:04
【问题描述】:

我正在为 Scheme (R5RS) 中的类 C 语言编写一个小型解释器,并尝试转换如下内容:

for (i = 0; i < 100; i++)
{
    if (isprime(i)) continue;
    else /* do something with i */
}

到有效的Scheme(isprime 函数只是一个例子,并不重要)。

但是,经过一段时间的尝试,我无法找到一种有效/简单的方法来将等价的 continue 语句添加到 Scheme 中的 do 循环中。更好的是允许使用“继续”和“中断”的“for”宏。

我正在考虑改用 Common Lisp。这种事情在 CL 中会更容易吗?

【问题讨论】:

    标签: loops functional-programming scheme common-lisp


    【解决方案1】:

    我们可以把 FOR 写成一个宏。 Common Lisp 版本:

    (defmacro for ((var start end) &body body)
      (let ((block-name (gensym "BLOCK")))
        `(loop for ,var from ,start below ,end
               do (block ,block-name
                    (flet ((continue ()
                             (return-from ,block-name)))
                      ,@body)))))
    
    
    CL-USER 2 > (for (i 10 20)
                  (if (evenp i) (continue))
                  (print i))
    
    11 
    13 
    15 
    17 
    19 
    

    【讨论】:

      【解决方案2】:

      CL 的tagbody 是一个方便的目标:

      (let (i)
        (tagbody
           (setf i 0)
         body
           (if (isprime i)
               (go increment))
           (do-something-with i)
         increment
           (setf i (1+ i))
           (if (< i 100)
               (go body))))
      

      【讨论】:

      • 啊,Lisp 看起来很有潜力!
      【解决方案3】:

      我会选择continuations,就像这个伪方案示例一样。

      只需将当前执行点存储在一个延续中,并在适当的时候调用它。

      (call/cc (lambda break ; jump outside the for
        (for 0 100 (lambda i 
          (call/cc (lambda continue ; jump to the next iteration
            (if (isprime i)
              (continue)
              (break))))))))
      

      【讨论】:

      • 我认为这将是一个 call/cc (它可以很好地用于中断),但是为什么当你继续执行时这会执行增量?另外,我认为这是非常低效的,因为它必须在循环的每次迭代中设置一个延续。
      • Continue 基本上只是跳过了循环体的其余部分,这正是我的构造所做的。跳转根本不会干扰循环本身,因此之后会发生递增。我没有关于效率的数据,尽管你的猜测可能是正确的。试试看吧。
      • 什么是for?没有forhere...这是什么?
      【解决方案4】:

      实现这一点的直接 Scheme 方法就是重构您的代码:

      for (i = 0; i < 100; i++)
      {
          if (isprime(i)) continue;
          if (is_bad(i)) break;
          else /* do something with i */
      }
      

      (let loop ((i 0))
        (cond
           ((isprime i)       ;; continue the loop
               (loop (+ i 1))) 
           ((is_bad i)
               #f)            ;; break from the loop
           (else              ;; do something with i
               .......        ;; and then continue the loop
               (loop (+ i 1)))))
      

      如果你的循环体很复杂,并且你想从其嵌套结构的深处进行(continue)(break),要么让你的编译器以上述方式重组它,要么你可以使用call/cc 设置退出点为例如

      (call/cc (lambda (break)
        (let loop ((i 0))
          (call/cc (lambda (continue)
              ;; your loop logic here, 
              ;; potentially using
              ;;    (continue A)     ;; A is ignored
              ;; or
              ;;    (break B)        ;; B is immediately returned as
              ;;                     ;;   the overall loop's result
              ))
          ;; (continue _) continues here:
          (loop (+ i 1)))))
      

      Racket 有定界的延续,应该更有效。

      【讨论】:

        【解决方案5】:

        要在 Scheme 中实现这个特定的代码示例,您不需要 continuebreakcall/cc

        (let loop ((i 0))
          (when (< i 100)
              (if (prime? i)
                  (loop (add1 i)))
              (do-something-else)))
        

        【讨论】:

        • 很遗憾,我在 R5RS 标准中找不到“when”形式。假设它符合我的想法,那么这段代码是否有效。让我们假设它通过调用循环来继续。当它完成对循环的调用时会发生什么?然后它会继续并完成身体,即再执行一次(做其他事情)吗?当然,这个解决方案只有在 (loop (add1 i)) 是尾调用时才有效?还是我误解了命名 let 的工作原理?
        • 是的,当然,您可以在大多数特定情况下找到解决方法(我什至认为在函数式风格中更可取)。 OP 虽然要求 general 解决方案,其中 break/continue 已在使用中。
        • 实际上,我认为这确实会导致解决方案。请参阅下面的示例。
        • @Bill 你可以用if替换when
        • when 不是循环结构,而if 是执行一次? when 会不会更接近 whileloop - if
        【解决方案6】:

        我认为 Vijay 的答案可以以一种可行的方式进行扩展(很抱歉回答了我自己的问题,但无法弄清楚如何在评论中格式化代码):

        (let loop ((i 0))
            (define (next)
              (loop (+ i 1)))
            (call/cc 
              (lambda (break)
                (if (< i 100)
                 (begin
                   (if (isprime i)
                     (next)
                     (begin
                       (if (isbad i)
                         (break break))
                       (do-something)
                       (next))))))))
        

        这不是一个宏,但无疑会导致一个足够通用的宏。我很想看到任何改进。我对 Scheme 很陌生。

        【讨论】:

          【解决方案7】:

          我知道这已经晚了 8 年,但我认为这可能会对某人有所帮助。

          使用 Common Lisp 中的 iterate 构造,您可以使用 next-iteration 子句:

          (iter (for i from 0 to 100) 
            (if (isprime i)
                (next-iteration)
                (do-something-else)))
          

          【讨论】:

          • 不幸的是,iterate 不是 Common Lisp 标准的一部分,但只能作为外部库使用,例如通过 quicklisp。
          【解决方案8】:

          您也可以使用 throwcatch (elisp):

          (let ((j 0))
            (while (< j 10)
              (catch 'continue
                (setq j (1+ j))
                (if (= j 3) (throw 'continue t))
                (prin1 j))))
          
          1245678910nil
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-11-11
            相关资源
            最近更新 更多