For Development HEAD DRAFTSearch (procedure/syntax/module):

4.8 繰り返し

Special Form: do ((variable init [step]) …) (test expr …) body …

[R7RS base]

  1. init …を評価し、variable …をそれぞれの結果へと 束縛します。以降のステップはvariable …が束縛された環境で評価されます。
  2. testを評価します。真の値が得られたら、 expr …を順に評価し、最後のexprの結果を返り値とします。
  3. そうでなければ、body …を(副作用のために)評価します。
  4. それからstep …を評価し、それぞれの結果へ 新たなvariable …を束縛し、ステップ2から繰り返します。

次の例はループを10回繰り返し、その間iの値をjへと 累積して最後に返すコードです。

(do ((i 0 (+ i 1))
     (j 0 (+ i j)))
    ((= i 10) j)
  (print j))
 ⇒ 45 ; また、jの中間結果が途中で出力される

stepが省略された場合は、variableの元の値がそのまま持ち越されます。 exprが一つも無い場合は、testが真の値を返した時点でその値がそのまま do式の値となります。

do構文は括弧を多用するので、角括弧を併用してまとまりを視覚的に 強調するのを好む人もいます。よくある書き方は、それぞれの変数の束縛と、 テスト節全体に角括弧を使うという書き方です。

(do ([i 0 (+ i 1)]
     [j 0 (+ i j)])
    [(= i 10) j]
  (print j))

註:Common Lisp (や、多くの言語の“forループ”) とは違って、 繰り返しの度に新しいvariableが束縛されます。 次の例では5回ループして、それぞれiを閉じ込むクロージャのリストを 作っています。各クロージャを呼び出せば、それぞれが自身が作られた時のiを 閉じ込んでいることがわかります。

(define closures
  (do ([i 0 (+ i 1)]
       [c '() (cons (^[] i) c)])
      [(= i 5) (reverse c)]
    ))

((car closures))  ⇒ 0
((cadr closures)) ⇒ 1
Special Form: let name ((var init) …) body …

[R7RS base] 「名前付きlet」と呼ばれるlet式のバリエーションです。 この構文は、次の手続きを作り、それをnameに束縛して、 init …を引数として呼び出します。

(lambda (var ...) body ...)

この構文自体は繰り返しとは直接に関係ありませんが、 名前付きletのポイントは、上のlambda式がnameのスコープ内で作られることです。 すなわち、bodyからnameを再帰的に呼ぶことができます。したがって この構文は再帰によるループを書くのに非常に良く使われます (そのため、次の例のように作られる手続きをloopと名付けることが良く行われます)。

(let loop ([x 0] [y '()])
  (if (= x 10)
    y
    (loop (+ x 1) (cons x y))))
 ⇒ (9 8 7 6 5 4 3 2 1 0)

もちろん名前付きletは必ずループに使わなければならないということはありません。 nameを非末尾再帰呼び出ししても良いですし、他の高階関数に渡しても構いません。 名前付きletがあるのは、ローカルな再帰関数を定義する際の共通するパターンをうまく とらえているためです。doよりも名前付きletを好んで使うSchemerもいます。 名前付きletの方が柔軟性が高いからです。

以下の書き換え規則が、名前付きletのセマンティクスを正確に説明します。 letrecの使い方がちょっとひねってあるのは、bodyprocの スコープに含まれるけれどinitはそうではない、ということを表現するためです。

(let proc ((var init) ...) body ...)
 ≡
((letrec ((proc (lambda (var ...) body ...)))
   proc)
 init ...)
Macro: dotimes ([variable] num-expr [result]) body …
Macro: dolist ([variable] list-expr [result]) body …

Common Lispから輸入された、簡易なループ構文です。 これらは、body …における副作用を前提としているという点で あまりSchemeっぽくありませんが、 日常のスクリプティングでよく出てくるパターンを手軽に書けます。

dotimesは、num-exprで与えられる回数body …を繰り返すのに、 dolistは、list-exprで与えられるリストの各要素に対して body …を繰り返すのに使えます。body …の実行中、 variableはそれまで繰り返した回数(dotimesの場合)もしくは 現在のリスト要素(dolistの場合)に束縛されます。

(dotimes (n 5) (write n))
 ⇒ writes "01234"

(dolist (v '(a b c d e)) (write v))
 ⇒ writes "abcde"

variableを使う必要がなければ省略することができます。 次の例はyear!を10回出力します。

(dotimes (10) (print "yeah!"))

3番めの要素resultdotimesdolistに与えられた場合、 その式はすべての繰り返しが終わった後で評価され、 その結果がdotimes/dolistの値となります。 resultは、variableが繰り返しの総回数(dotimesの場合) もしくは()(dolistの場合)に束縛された環境で評価されます。 これはCommon Lispに倣っています。

Common Lispと違い、新たなvariableが繰り返しの度に束縛されます (Common Lispでは、一つのvariableが繰り返しごとに変更を受けます)。 したがって、variableを綴じ込むクロージャを作った場合でも、 繰り返しの進行でvariableが上書きされることはありません。

単純な繰り返し以上のことがしたいなら、doフォーム、名前付きlet、あるいは srfi.42 - 先行評価的内包表記も参照してください。 より複雑な繰り返しを記述できます。

Macro: while expr body …
Macro: while expr => var body …
Macro: while expr guard => var body …

varは識別子でguardは一つの引数をとる手続きです。

最初の形式ではまずexprが評価され、もしそれが真値を返したら body … が評価されます。そしてexprが真値を返す 限り繰り返されます。

2番目の形式では、body … で有効な変数varを 作成し、exprの結果に束縛します。

最後の形式では、expr の返り値が guard に渡されこの 結果が真値である限り body … が繰り返されます。 変数 varexpr の結果に束縛されます。

while 自体の返り値は不定です。

(let ((a '(0 1 2 3 4)))
  (while (pair? a)
    (write (pop! a)))) ⇒ prints "01234"

(let ((a '(0 1 2 3 #f 5 6)))
  (while (pop! a) integer? => var
    (write var))) ⇒ prints "0123"
Macro: until expr body …
Macro: until expr guard => var body …

while の条件を逆にしたものです。 つまり、 最初の形式ではexprが真値を返すまで body … を 繰り返します。2番目の形式ではexprの結果がguard に渡され それが真値を返すまで繰り返します。 Varexpr返り値に 束縛されます。

(guard を省いた2番目の形式ではvarは常に#fへ 束縛されるため、あまり意味がありません。)

until自体の返り値は不定です。

(let ((a '(0 1 2 3 4)))
  (until (null? a)
    (write (pop! a)))) ⇒ prints "01234"

(until (read-char) eof-object? => ch
  (write-char ch))
 ⇒ reads from stdin and writes char until EOF is read


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT