"do" 控制结构在 Scheme 中有什么作用?

What does the "do" control construct do in Scheme?

我 运行 反对 Scheme 程序,它使用所谓的 do 程序工作,但我不知道它是如何工作的,也不知道它是如何实现的。如果有人可以提供帮助,我将不胜感激。 代码如下:

(define (storage-move-right vector i j)
  (do ((idx j (- idx 1)))
    O(n)
      ((< idx i))
        (vector-set! vector (+ idx 1) (vector-ref vector idx))))

"missing" 循环构造 while、repeat-until 和 for

问题

控制结构 whilerepeat-untilfor 存在于大多数主流编程语言中,但在 Scheme 标准中却找不到它们。 Scheme中用到了什么?

解决方案

这些结构都可以 "emulated" 通过使用正常的(尾递归)函数调用。然而,由于这些结构经常被使用,所以有一些更方便的语法糖可用。两个最常用的构造是使用 named letdo 构造。

最好将 do 构造视为美化的 "do-until" 循环。 do 的最简单形式是:

(do ()
  [test return-exp]
  exp
  ...)

如果省略 return-exp 则 returned 值为 void、"the invisible value"(如此命名是因为它没有在交互中打印 window).

在每个循环开始时,都会计算测试表达式。如果它不为假,则计算 return-exp 并且其 return 值是整个 do 循环的值。如果测试为假,则按顺序评估主体表达式。在对最后一个主体表达式求值后,开始一个新的循环。

; Example 1
; WARNING: This is not good style.
;          A better solution is found below.

                                  ; Pseudo Code
(define i 0)                      ; i := 0
(do ()                            ; do 
  [(= i 5)]                       ;   until i=5
  (display i)                     ;       display i
  (set! i (+ i 1)))               ;       i := i +1

; displays: 01234

do 的替代方法 "named let" 是 let 语法的变体,它提供了比 do 更通用的循环结构,也可用于表达递归。它具有与普通 let 几乎相同的语法,但是可以在主体中使用变量名称来重复执行带有 var1 的新值的主体...通过使用新的名称调用名称值。

(let name ([var1 exp1]
           ...)
   body
   ...)

变量列表可以为空。 重写一个普通的 while 循环现在很容易:

while test           (do ()                 (let while ()
  begin                [(not test)]           (when test
    exp1               exp1                      exp1
    exp2               exp2                      exp2
    ...                ...)                      ...
  end                                            (while)))

(void) 表达式的计算结果为 "invisible" 值,表示不打算使用 return 值。它不是由交互 window(REPL)打印的。

要用 do 编写一个 for 循环需要我们看下一个最简单的 do 形式:

(do ([var start-exp next-exp])
  [test return-exp]
  exp
  ...)

do 表达式的计算从 start-exp 的计算开始。结果绑定到变量 i,它在 next-exp 以及 testreturn-expbody 表达式中都可见。当循环重新启动时,next-exp 被评估并且变量 var 被重新计算到结果,在 test 表达式和(可能)主体被重新评估之前。

; Example 2
(do ([i 0 (+ i 1)])           (let loop ([i 0])
  [(= i 5)]                     (unless (= i 5)
  (display i))                     (display i)
                                   (loop (+ i 1))))
; displays: 01234

现在很清楚如何用do写一个普通的for循环了:

for i=a to b step d         (do ([i a (+ i d)])          (let for ([i a])
  begin                       [(> i b)]                    (unless (> i b)
    exp1                      exp1                            exp1
    exp2                      exp2                            exp2
    ...                       ...)                            ...
  end                                                         (for (+ i d))))

repeat-until循环使用do写起来很尴尬,但是使用named let我们可以做如下:

repeat                
  begin        (let loop ()         (let loop ()
    exp1         exp1                 exp1
    exp2         exp2                 exp2
    ...          ...                  ...
  end            (if (not test)       (unless test (loop)))
until test           (loop)))

此答案基于 SchemeCookbook 中的条目。查看档案:https://web.archive.org/web/20131004034526/http://schemecookbook.org/Cookbook/IdiomLoopingConstructs