オフィスアワーがそろそろ始まるよ!()

繰り返し

処理を繰り返すためのwhile / forについて説明します。

while

whileは条件を満たす間、同じ処理を繰り返します。while (条件式) { ... }が基本構文です。条件式には、ブール型、オプション型、エラー共用体が入ります。条件式がtruenullでない、エラー型でない、間はブロック内の処理を繰り返します。

次のプログラムは、i10より小さい間、i += 1を実行します。i0で初期化されているため、都合10回ループが繰り返されることになります。whileループを抜けた直後、iの値は10になっています。

examples/ch04-basic-syntax/src/while.zen:4:10

test "while" {
    var i: u32 = 0;
    while (i < 10) {
        i += 1;
    }
    ok(i == 10);
}

変化式

毎ループ繰り返す処理を記述することができます。while (条件式) : (変化式) { ... }という構文を使います。ブロック ({}) も式のため、変化式として書くことができます。

次のコードは、以前の例でブロック内に書かれていた処理を変化式として書くように直したものです。

examples/ch04-basic-syntax/src/while.zen:12:16

test "while with continue expression" {
    var i: u32 = 0;
    while (i < 10) : (i += 1) {}
    ok(i == 10);
}

複数の式を変化式内に書きたい場合は、ブロックを使用します。

examples/ch04-basic-syntax/src/while.zen:18:24

test "block as continue expression" {
    var i: u32 = 0;
    var j: u32 = 0;
    while (i < 10 and j < 10) : ( {i += 1; j += 1;} ) {}
    ok(i == 10);
    ok(j == 10);
}

continue

continue以降のブロック内の処理を実行せず、ループの先頭に戻ります。

examples/ch04-basic-syntax/src/while.zen:26:38

test "continue" {
    var num_even: u32 = 0;
    var i: u32 = 0;
    while (i < 10) : (i += 1) {
        if (i % 2 == 0) {
            num_even += 1;
            continue;
        }
        // `i`が奇数の時のみ、実行される
        ok((i % 2) == 1);
    }
    ok(num_even == 5);
}

iが偶数の場合、num_even += 1;が実行された後、continue;によりループの先頭に戻ります。上のコードでcontinue;がない場合には、iが偶数の場合にもok((i % 2) == 1);が実行されてしまい、テストが失敗します。

continueでループの先頭に戻った場合も、変化式が実行されることに注意して下さい。

break

breakを使ってループを途中で終了させることができます。以下のコードでは、whileの条件式ではなく、break;でループを終了しています。

examples/ch04-basic-syntax/src/while.zen:40:51

test "break in while" {
    var i: u32 = 0;
    // `i`が10より小さい間ループするwhile
    while (i < 10) : (i += 1) {
        // `i`が`5`の場合、ループを抜ける
        if (i == 5) {
            break;
        }
    }
    // `i`は5までしか増えていない
    ok(i == 5);
}

Zenではwhileもまたであるため、値を返すことができます。whileのブロック内で値を返す場合も、breakを使用します。whileのブロック内からbreakで値を返す場合、whileブロック内でbreakされなかった場合のためのelseが必要です。

次のコードは、第一引数で与えたスライス (slice) 内に、第二引数で与える数値 (number) が含まれているかどうか、をブール型で返します。numbersliceに含まれている場合、break truetrueresultに格納されます。一方、含まれていない場合は、whileループを抜けて、else falseによりfalseresultに格納されます。

examples/ch04-basic-syntax/src/while.zen:53:62

fn sliceHasEvenNumber(slice: []const u32, number: u32) bool {
    var i: u32 = 0;
    const result = while (i < slice.len) : (i += 1) {
        if (slice[i] == number) {
            break true;
        }
    } else false;

    return result;
}

上記コードを使う例は以下の通りです。

examples/ch04-basic-syntax/src/while.zen:64:72

test "while expresssion" {
    const slice = [_]u32{ 1, 3, 5, 7 };

    var result = sliceHasEvenNumber(slice, 5);
    ok(result == true);

    result = sliceHasEvenNumber(slice, 6);
    ok(result == false);
}

elseを省略すると、while式の返り値型がvoid型になるので、コンパイルエラーになります。

fn sliceHasEvenNumber(slice: []const u32, number: u32) bool {
    var i: u32 = 0;
    const result = while (i < slice.len) : (i += 1) {
        if (slice[i] == number) {
            break true;
        }
    };  // `else`を省略

    return result;
}
error[E02047]: unable to convert 'void' to 'bool'
    const result = while (i < slice.len) : (i += 1) {
                   ~

ラベル付きwhile

whileループにラベルをつけ、continuebreak時にラベルを参照することができます。これはネストしたループを作成した時に便利です。

2重にネストされたwhileループの例を示します。中のループから外側のループのラベルを参照してcontinueしています。

examples/ch04-basic-syntax/src/while.zen:74:85

test "labeled while" {
    var i: u32 = 0;
    outer: while (i < 10) : (i += 1) {
        var j: u32 = 0;
        while (j < 10) : (j += 1) {
            // 中のループに入ると外のループの最初に戻る
            continue :outer;
        }
        // ここには来ない
        ok(false);
    }
}

次は、breakの例です。

examples/ch04-basic-syntax/src/while.zen:87:98

test "labeled while" {
    var i: u32 = 0;
    outer: while (i < 10) : (i += 1) {
        var j: u32 = 0;
        while (j < 10) : (j += 1) {
            // 中のループに入ると外のループごと抜ける
            break :outer;
        }
    }
    // 外のループも最初の1回しか実行されない
    ok(i == 0);
}

オプション型

ifのように、whileループでもオプション型を条件式として受け取り、中の値をキャプチャすることができます。もし、オプション型の値を評価した結果がnullであれば、ループを終了します。ループを終了した時の処理は、elseに記述することができます。

examples/ch04-basic-syntax/src/while.zen:100:111

fn returnNullIfZero(a: u64) ?u64 {
    return if (a == 0) null else a;
} 

test "optional while" {
    var num: u64 = 3;
    while (returnNullIfZero(num)) |value| {
        num -= 1;
    } else {
        ok(num == 0);
    }
}

エラー共用体

ifのように、whileループでもエラー共用体を条件式として受け取り、中の値をキャプチャすることができます。もし、エラー共用体の値を評価した結果がエラー型であれば、ループを終了します。ループを終了した時には、elseでエラー型の値をキャプチャして、エラー処理しなければなりません。

examples/ch04-basic-syntax/src/while.zen:113:124

fn returnErrorIfZero(a: u64) !u64 {
    return if (a == 0) error.IsZero else a;
} 

test "error union while" {
    var num: u64 = 3;
    while (returnErrorIfZero(num)) |value| {
        num -= 1;
    } else |err| {
        ok(err == error.IsZero);
    }
}

for

繰り返しを実現するもう1つの手段はforです。forはスライスまたは配列の要素を繰り返し処理します。基本構文はfor (スライス / 配列) |要素のキャプチャ| { ... }です。

下記のコードでは、配列itemsの要素を順番に処理しています。各配列要素を|value|でキャプチャしています。

examples/ch04-basic-syntax/src/for.zen:4:12

test "for" {
    const items = [_]u32{ 1, 2, 3, 4 };
    var sum: u32 = 0;

    for (items) |value| {
        sum += value;
    }
    ok(sum == 10);
}

配列要素の要素数を得たい場合は、|value, i|と書くと2つ目の識別子iに要素数を格納することができます。

examples/ch04-basic-syntax/src/for.zen:14:19

test "get index" {
    const items = [_]u32{ 0, 1, 2, 3 };
    for (items) |value, i| {
        ok(value == i);
    }
}

参照をキャプチャするfor

処理するスライス / 配列の要素を更新する場合は、要素への参照をキャプチャします。要素への参照をキャプチャするためには、先頭にアスタリスク (*) を付けて|*要素のキャプチャ|とします。

examples/ch04-basic-syntax/src/for.zen:21:30

const equalSlices = std.testing.equalSlices;
test "for reference" {
    var items = [_]u32{ 1, 2, 3, 4 };
    
    for (items) |*value| {
        // `items`内の値を書き換える
        value.* += 1;
    }
    equalSlices(u32, [_]u32{ 2, 3, 4, 5 }, items);
}

for else

Zenのforは式なので、値を返すことができます。whileの時と同様に、breakでブロック内から値を返すことができます。forのブロック内でbreakしなかった場合のために、elseで同じ型の値を返します。

examples/ch04-basic-syntax/src/for.zen:32:40

fn sliceHasEvenNumber(slice: []const u32, number: u32) bool {
    const result = for (slice) |value| {
        if (value == number) {
            break true;
        }
    } else false;

    return result;
}

ラベル付きfor

whileと同様に、forループにもラベルを付けることができます。continuebreakを使った例を、それぞれ示します。

examples/ch04-basic-syntax/src/for.zen:62:79

test "labeled for" {
    const array2D = [_][4]u32 {
        [_]u32 { 1, 2, 3, 4 },
        [_]u32 { 5, 6, 7, 8 },
    };
    var result: u32 = 0;
    outer: for (array2D) |array1D| {
        for (array1D) |value| {
            // 中の配列の第一要素だけ`result`に加算して外のループに戻る
            result += value;
            continue :outer;
        }
        // ここには来ない
        ok(false);
    }

    ok(result == 6);
}

examples/ch04-basic-syntax/src/for.zen:81:96

test "labeled for" {
    const array2D = [_][4]u32 {
        [_]u32 { 1, 2, 3, 4 },
        [_]u32 { 5, 6, 7, 8 },
    };
    var result: u32 = 0;
    outer: for (array2D) |array1D| {
        for (array1D) |value| {
            // 中の配列の第一要素だけ`result`に加算して外のループに戻る
            result += value;
            break :outer;
        }
    }

    ok(result == 1);
}```

☰ 人の生きた証は永遠に残るよう ☰
Copyright © 2018-2019 connectFree Corporation. All rights reserved.
Zen, the Zen three-circles logo and The Zen Programming Language are trademarks of connectFree corporation in Japan and other countries.