[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

4.8 繰り返し

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

[R7RS]

  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] 「名前付き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] expr [result]) body …
Macro: dolist ([variable] expr [result]) body …

Common Lispからの輸入です。完全なフォームは以下のように展開されます。

 
(dotimes (variable expr result) body …)
==>
(do ((limit expr)
     (variable 0 (+ variable 1)))
    ((>= variable expr) result)
  body …)

(dolist (variable expr result) body …)
==>
(begin
  (for-each (lambda (variable) body …) expr)
  (let ((variable '())) result))

(dolistresultの評価時にvariable()に 束縛しているのはCLとの互換性のためです。)

result、もしくはresultvariable両方を省略することが できます。つまり、最初の引数が2要素であれば、それはvariableexprだということです。その場合、これらのフォームの結果は未定義です。

もしresultvariableの両方が省略されたら、 exprで決定される回数だけ単にbody …を繰り返し実行します。

 
;; print "a" 10 times.
(dotimes (10) (print "a")) 

;; print "a" (length lst) times.
(dolist (lst) (print "a"))

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

[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated on July 19, 2014 using texi2html 1.82.