Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第8章. 有限の繰り返し

この章の目的

この章を読み終える頃には、ループを書けるようになり、Keleusmaのループが他のほとんどの言語のループと違う、たった一つのルールを理解しているはずです。この章を読み終える頃には、ループを書けるようになり、Keleusmaのループが他のほとんどの言語のループと違う、たった一つのルールを理解しているはずです。

回数の決まったループ

楽譜の繰り返し記号は、何小節繰り返すかを示します。「とりあえずしばらく繰り返してみる」とは決して書きません。Keleusmaのループも同じです。すべてのループは、開始する前に回数が決まっています。これがこの章のタイトルにある「有限の」という言葉の意味です。楽譜の繰り返し記号は、何小節繰り返すかを示します。「とりあえずしばらく繰り返してみる」とは決して書きません。Keleusmaのループも同じです。すべてのループは、開始する前に回数が決まっています。これがこの章のタイトルにある「有限の」という言葉の意味です。

ループはforで書きます。数の範囲か、配列の要素を一つずつたどります。

fn main() -> Word {
    let durations = [4, 4, 8, 2];

    for d in durations {
        let _step = d * 2;
    }

    for beat in 0..4 {
        let _tick = beat;
    }

    durations[2]
}

keleusma runで実行します。出力は次のとおりです。

8

最初のループは配列durationsの4つの要素を順に巡り、毎回dに束縛します。次のループは範囲0..4を巡ります。これは0、1、2、3という4つの数字です。どちらのループも、ループが始まる前に回数が決まっています。配列には決まった長さがあり、範囲は自分の上限を宣言しています。最初のループは配列durationsの4つの要素を順に巡り、毎回dに束縛します。次のループは範囲0..4を巡ります。これは0、1、2、3という4つの数字です。どちらのループも、ループが始まる前に回数が決まっています。配列には決まった長さがあり、範囲は自分の上限を宣言しています。

正直な制限

このプログラムをよく見てください。2つのループは走りますが、結果の8はそれに依存していません。8はdurations[2]、つまり配列の位置2にある値で、ループを使わずに計算しています。このプログラムをよく見てください。2つのループは走りますが、結果の8はそれに依存していません。8はdurations[2]、つまり配列の位置2にある値で、ループを使わずに計算しています。

これは意図的なもので、第5章から直接導かれます。束縛は再代入できません。だからループは合計を加算していくことができません。なぜなら、合計とは毎回の周回で変わる値だからです。fnという種類の宣言で書かれた、原子的な関数の中では、ループは値を巡ることはできても、結果を積み上げていくことはできません。これは意図的なもので、第5章から直接導かれます。束縛は再代入できません。だからループは合計を加算していくことができません。なぜなら、合計とは毎回の周回で変わる値だからです。fnという種類の宣言で書かれた、原子的な関数の中では、ループは値を巡ることはできても、結果を積み上げていくことはできません。

ではなぜループを今学ぶのかというと、ループは第4部で活躍するからです。そこではloopという別の種類の関数の中で、ループの各周回がホストに値を渡したり、保存された状態を更新したりして、繰り返しが本当の仕事をするようになります。この章ではループの形を学んでおきます。あとで本当に必要になったときには、形が見覚えあるものになっているはずです。ではなぜループを今学ぶのかというと、ループは第4部で活躍するからです。そこではloopという別の種類の関数の中で、ループの各周回がホストに値を渡したり、保存された状態を更新したりして、繰り返しが本当の仕事をするようになります。この章ではループの形を学んでおきます。あとで本当に必要になったときには、形が見覚えあるものになっているはずです。

ループを早めに抜ける

ループは回数に到達する前に止めることもできます。breakを使います。

for beat in 0..16 {
    if beat == 4 {
        break;
    }
}

beatが4に到達すると、breakがループを即座に終わらせます。breakのあとにあるセミコロンに注意してください。breakでループを早めに止められたとしても、回数はやはり有限です。ループは最大でも宣言した回数まで(あるいはそれより少なく)しか走らず、それを超えることはありません。beatが4に到達すると、breakがループを即座に終わらせます。breakのあとにあるセミコロンに注意してください。breakでループを早めに止められたとしても、回数はやはり有限です。ループは最大でも宣言した回数まで(あるいはそれより少なく)しか走らず、それを超えることはありません。

A cap for a runtime range

Every loop so far has had a count known before the loop begins, either a literal like 0..8 or the fixed length of an array. A loop whose end comes from a value computed at run time, like 0..n where n is a parameter, is rejected, because the tool cannot see how many passes it will take and so cannot bound its time and memory.

You can supply that bound yourself with a limit. The loop then runs over its real range, but never more times than the cap you name:

for i in 0..n limit 64 {
    // runs at most 64 times, and stops early when i reaches n
}

The cap must be a constant the tool can read at compile time, so a plain number, a const-data field, or a const parameter. The worst-case count is the cap, which is what makes the loop admissible even though n is only known at run time.

If the range is longer than the cap, the loop stops at the cap. By default that is treated as a mistake and the program stops with a loud error, rather than quietly leaving work undone. If stopping at the cap is something you want to handle, add an on block that names the outcomes:

for i in 0..n limit 64 {
    // body, may break
} on {
    ok(count)   => { /* the range finished */ },
    break(at)   => { /* the body ran break; at is the index */ },
    limit(at)   => { /* the cap was reached first; at is the index */ },
}

An on block always names an ok arm, the same catch-all every other handling block in the language requires; the break and limit arms are optional, and a break you do not name runs the ok arm. Without an on block at all, reaching the cap is the loud error just described, while finishing the range or breaking simply ends the loop. The bound is the cap in every case, so the loop always has a worst-case count the tool can prove.

この章のまとめ

  • for name in 0..n { ... }は数の範囲を巡ります。
  • for name in array { ... }は配列の要素を巡ります。
  • すべてのループは、開始する前に回数が決まっています。
  • 原子的なfnの中では、ループは結果を積み上げることはできません。束縛が変わらないからです。ループが本当の仕事をするのは第4部です。原子的なfnの中では、ループは結果を積み上げることはできません。束縛が変わらないからです。ループが本当の仕事をするのは第4部です。
  • break;でループを早めに抜けます。
  • limit puts a compile-time cap on a runtime range, and an on block captures how the loop ended (ok, break, or limit).

次の章では、値を関数の連なりに通します。