[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