在Scheme/Lisp中的for/continue

6
我是一名程序员,正在用 Scheme (R5RS) 编写一个类 C 语言的小型解释器,并尝试将以下代码进行转换:
for (i = 0; i < 100; i++)
{
    if (isprime(i)) continue;
    else /* do something with i */
}

我希望验证Scheme代码(isprime函数只是一个示例,不重要)。

然而,经过一段时间的尝试,我没有找到在Scheme中向do循环添加continue语句的有效/简单方法。更好的是,“for”宏允许使用“continue”和“break”。

我正在考虑转换到Common Lisp。在CL中是否更容易实现这种操作?

8个回答

8
我们可以将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 

3

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看起来有一些潜力! - Bill

1

我会选择像在这个伪Scheme示例中使用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(对于break来说效果很好),但为什么在使用continue时会执行增量操作呢?此外,我认为这非常低效,因为它必须在每次循环迭代时设置一个continuation。 - Bill
1
“Continue” 基本上只是跳过循环体的其余部分,这正是我的结构所做的。跳转不会干扰循环本身,因此递增将在之后发生。我没有关于效率的数据,但你的猜测可能是正确的。只需尝试即可。 - Dario
什么是 for?在这里没有 for(https://www.gnu.org/software/guile/manual/html_node/R5RS-Index.html#R5RS-Index_rn_letter-F)...那是什么? - Will Ness

1
我知道这已经晚了8年,但我认为它可能会对某些人有所帮助。
使用Common Lisp中的iterate结构,您可以使用下一个迭代子句:
(iter (for i from 0 to 100) 
  (if (isprime i)
      (next-iteration)
      (do-something-else)))

很不幸,“iterate” 不是 Common Lisp 标准的一部分,只能作为外部库使用,例如通过 quicklisp。 - GuentherT

1
直接使用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具有分界限制的延续,应该更有效率。

0

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

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

很遗憾,在R5RS标准中我找不到“when”表单。假设它的功能与我想的一样,那么这段代码是否有效?假设它通过调用循环来继续执行。当它完成对循环的调用时会发生什么?它会继续并完成主体,即执行一个更多的(do-something-else)吗?如果(loop (add1 i))是尾调用,那么这个解决方案肯定可行。或者我是否误解了命名let的工作原理? - Bill
当然,在大多数特定情况下,您可以找到解决方法(我甚至认为在函数式风格中更可取)。但是,OP要求一个通用解决方案,其中break/continue已经在使用中。 - Dario
实际上,我认为这确实会导致一个解决方案。请参见下面的示例。 - Bill
@Bill 你可以用 if 替换 when - Vijay Mathew
when不是循环结构,而if是一次性执行的吗?when不更接近于whileloop - if吗? - Olie

0

我认为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 还相当新。


0

你也可以使用 throwcatchelisp):

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

1245678910nil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接