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

パターンマッチ

ifelse ifにより複数の条件を分岐させることが可能ですが、プログラムの見通しが悪くなります。そこでswitchを使います。Zenのswitchには非常に強力な機能が備わっています。

switchの基本

switchの基本構文は次の通りです。

switch (式) {
    パターン => 処理,
    パターン => 処理,
    ...
    else => 処理,
}

の値とパターンがマッチした場合、=>演算子の右の処理が実行されます。パターンの部分には、1つの値、,で区切られた複数の値、...で指定された範囲、else、のいずれかを書くことができます。

switchの値が取りうる全ての値を網羅しなければなりません。もし式がu8の値だったとすると、switchでは0から255までの数値を網羅しなければなりません。興味のある数値が10より小さいものだけだったりすると、全ての条件を網羅するのは大変です。そこで、elseを使用することができます。

実際のコードを見てみましょう。同じコードをif / if else / elseで書く場合と比較してみて下さい。ずいぶんとスッキリ書けることがわかります。

examples/ch04-basic-syntax/src/switch.zen:4:18

test "switch basic" {
    var x: u8 = 10;
    switch (x) {
        // `x`が0の場合
        0 => std.debug.warn("zero\n"),
        // `x`が1か2か3の場合
        // `,`で条件を複数組み合わせられる
        1, 2, 3 => std.debug.warn("one, two or three\n"),
        // `x`が4以上9以下の場合
        // `...`で条件を範囲で指定できる
        4...9 => std.debug.warn("four to nine\n"),
        // 上記条件のいずれにもマッチしなかった場合
        else => std.debug.warn("ten or more\n"),
    }
}

このコードのstd.debug.warnの出力結果は次の通りです。

ten or more

パターンの値は重複があってはなりません。

    var x: u8 = 12;
    switch (x) {
        12 => {},
        10...15 => {},
        else => {},
    }

このコードは、次のコンパイルエラーになります。

error[E02016]: duplicate value 12
        12 => {},
        ~
note[E00001]: declared here
        10...15 => {},
        ~

switchのパターンは、コンパイル時計算可能であれば、変数や複雑な計算式を置くことができます。

examples/ch04-basic-syntax/src/switch.zen:20:36

test "switch case cannot fallthrought" {
    const ZERO = 0;
    var x: u8 = 2;
    switch (x) {
        // `ZERO`はコンパイル時計算可能
        ZERO => std.debug.warn("zero\n"),
        // `ZERO + 1`はコンパイル時計算可能
        ZERO + 1 => std.debug.warn("one\n"),
        comptime TWO: {
            const a: u32 = 10;
            const b: u32 = 8;
            break :TWO a - b;
        } // ここまでがパターン。パターンは`a-b`の結果`2`になる
            => std.debug.warn("two\n"),
        else => std.debug.warn("three or more\n"),
    }
}

comptimeについては、12.1 comptimeで解説します。

=>の右辺には、ブロック ({}) を書くこともできます。このことにより、複数の文を書くことができます。

examples/ch04-basic-syntax/src/switch.zen:38:49

test "switch case with block" {
    var x: u8 = 4;
    switch (x) {
        // ブロック内に処理を書ける
        0...10 => {
            const is_even = (x % 2) == 0;
            if (is_even) {
                std.debug.warn("0, 2, 4, 6, 8 or 10\n");
            }           
        },
        else => std.debug.warn("ten or more\n"),
    }
}

パターンにマッチした時の値を別変数にキャプチャすることもできます。このとき、マッチした値を参照としてキャプチャすることもできます。

examples/ch04-basic-syntax/src/switch.zen:52:64

test "switch case with capture" {
    var x: u8 = 4;
    switch (x) {
        // `ten_or_less`に値をキャプチャ
        0...10 => |ten_or_less| {
            std.debug.warn("number is: {}\n", ten_or_less);        
        },
        // `x`の参照をキャプチャ
        else => |*more_than_ten| { 
            std.debug.warn("number is: {}\n", more_than_ten.*);
        },
    }
}

switch式

Zenのswitchは式であるため、値を返すことができます。ラベル付きブロックを使用して複雑な計算を行うことも可能です。

examples/ch04-basic-syntax/src/switch.zen:66:79

test "switch expression" {
    var x: u8 = 6;
    const result = switch (x) {
        // `,`の前の値を返す
        0...5 => true,
        // ラベル付きブロックで`break`された値を返す
        else => six_or_more: {
            std.debug.warn("number is six or more\n");
            break: six_or_more false;
        },
    };

    ok(result == false);
}

列挙型とswitch

switchが最も活躍する場面は、列挙型 (およびタグ付き共用体) と組み合わせて利用する時です。重複なく全てのヴァリアントを処理しないとコンパイルエラーになります。

examples/ch04-basic-syntax/src/switch_enum.zen:5:21

const Colors = enum {
    Red,
    Green,
    Blue,
};

test "switch with enum" {
    var color = Colors.Red;
    const result = switch (color) {
        // `.`で列挙型を型推論できる
        .Red => u32(0),
        .Green => u32(1),
        .Blue => u32(2),
    };

    ok(result == 0);
}

次のコードでは、Blueのケースを書き忘れています。

    const result = switch (color) {
        .Red => u32(0),
        .Green => u32(1),
        // Blueのケースを書き忘れ
    };

このコードは次のコンパイルエラーになります。

error[E02018]: 'Color.Blue' not handled in switch
    const result = switch (color) {
                   ~

Zenの標準ライブラリでは、プラットフォームごとの違いをswitchとコンパイラ組込みの列挙型とで吸収しています。例えば、OSごとに (コンソールの文字) 色を変えたいとします。その場合、次のコードのように、builtin.osの列挙型でswitchし、os_colorという共通して使える定数に、各OSで使う色を代入しておきます。

examples/ch04-basic-syntax/src/switch_enum.zen:23:27

const os_color = switch(builtin.os) {
    .macosx => .Red,
    .linux => .Green,
    else => .Blue,
};

上記コードにより、下のコードではプラットフォームごとにos_colorが異なった値を持ちます。

examples/ch04-basic-syntax/src/switch_enum.zen:29:35

test "switch with builtin enum" {
    switch (builtin.os) {
        .macosx => ok(os_color == .Red),
        .linux => ok(os_color == .Green),
        else => ok(os_color == .Blue),
    }
}

タグ付き共用体に対するswitch

タグ付き共用体に対してswitchを利用することができます。このことにより、異なるデータ構造switchで容易に取り扱うことができます。非常に強力な機能なので、詳しく見ていきましょう。

IPv4 (8ビット×4) アドレスとIPv6 (16ビット×8) アドレスとを表現できるIpAddrタグ付き共用体を考えます。IpAddrはヴァリアントのipv4ipv6とを持ちます。それぞれのヴァリアントをIPv4とIPv6の作法に則って表示するprinAddress関数があります。

examples/ch04-basic-syntax/src/switch_tagged_union.zen:4:23

const IpAddr = union(enum) {
    ipv4: [4]u8,
    ipv6: [8]u16,

    fn printAddress(self: IpAddr) void {
        switch(self) {
            // IPv4の場合、`addr`にヴァリアントの値`[4]u8`をキャプチャ
            .ipv4 => |addr| {
                std.debug.warn("{}:{}:{}:{}\n",
                    addr[0], addr[1], addr[2], addr[3]);
            },
            // IPv6の場合、`addr`にヴァリアントの値`[8]u16`をキャプチャ
            .ipv6 => |addr| {
                std.debug.warn("{x}:{x}:{x}:{x}:{x}:{x}:{x}:{x}\n",
                    addr[0], addr[1], addr[2], addr[3],
                    addr[4], addr[5], addr[6], addr[7]);
            },
        }
    }
};

printAddressではswitchを使用してipv4ipv6のヴァリアントをパターンマッチしています。selfで受け取ったIpAddrの値がipv4ヴァリアントであればIPv4の形式で、ipv6のヴァリアントであればIPv6の形式でアドレスが表示されます。

examples/ch04-basic-syntax/src/switch_tagged_union.zen:25:35

test "swith with tagged union" {
    const ipv4 = IpAddr { .ipv4 = [_]u8{ 192, 168, 0, 1 } };
    const ipv6 = IpAddr {
        .ipv6 = [_]u16 {
            0x2001, 0x0db8, 0xbd05, 0x01d2, 0x288a, 0x1fc0, 0x0001, 0x10ee
        }
    };

    ipv4.printAddress();
    ipv6.printAddress();
}

上記コードを実行すると、std.debug.warnは次の結果を出力します。

192:168:0:1
2001:db8:bd05:1d2:288a:1fc0:1:10ee

☰ 人の生きた証は永遠に残るよう ☰
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.