[R7RS base]
次の例はループを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
[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の使い方がちょっとひねってあるのは、bodyはprocの
スコープに含まれるけれどinitはそうではない、ということを表現するためです。
(let proc ((var init) ...) body ...) ≡ ((letrec ((proc (lambda (var ...) body ...))) proc) init ...)
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番めの要素resultがdotimesやdolistに与えられた場合、
その式はすべての繰り返しが終わった後で評価され、
その結果がdotimes/dolistの値となります。
resultは、variableが繰り返しの総回数(dotimesの場合)
もしくは()(dolistの場合)に束縛された環境で評価されます。
これはCommon Lispに倣っています。
Common Lispと違い、新たなvariableが繰り返しの度に束縛されます (Common Lispでは、一つのvariableが繰り返しごとに変更を受けます)。 したがって、variableを綴じ込むクロージャを作った場合でも、 繰り返しの進行でvariableが上書きされることはありません。
単純な繰り返し以上のことがしたいなら、doフォーム、名前付きlet、あるいは
srfi.42 - 先行評価的内包表記も参照してください。
より複雑な繰り返しを記述できます。
=> var body … ¶=> var body … ¶varは識別子でguardは一つの引数をとる手続きです。
最初の形式ではまずexprが評価され、もしそれが真値を返したら body … が評価されます。そしてexprが真値を返す 限り繰り返されます。
2番目の形式では、body … で有効な変数varを 作成し、exprの結果に束縛します。
最後の形式では、expr の返り値が guard に渡されこの 結果が真値である限り body … が繰り返されます。 変数 var は expr の結果に束縛されます。
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"
=> var body … ¶while の条件を逆にしたものです。 つまり、
最初の形式ではexprが真値を返すまで body … を
繰り返します。2番目の形式ではexprの結果がguard に渡され
それが真値を返すまで繰り返します。 Varはexpr返り値に
束縛されます。
(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