if
とelse if
により複数の条件を分岐させることが可能ですが、プログラムの見通しが悪くなります。そこでswitch
を使います。Zenの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 patterns computed in comptime" {
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.*});
},
}
}
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
が最も活躍する場面は、列挙型 (およびタグ付き共用体) と組み合わせて利用する時です。重複なく全てのヴァリアントを処理しないとコンパイルエラーになります。
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 => @to(u32, 0),
.Green => @to(u32, 1),
.Blue => @to(u32, 2),
};
ok(result == 0);
}
次のコードでは、Blue
のケースを書き忘れています。
const result = switch (color) {
.Red => @to(u32, 0),
.Green => @to(u32, 1),
// Blueのケースを書き忘れ
};
このコードは次のコンパイルエラーになります。
error[E02018]: 'Color.Blue' not handled in switch
const result = switch (color) {
~
Zenの標準ライブラリでは、プラットフォームごとの違いをswitch
とコンパイラ組込みの列挙型とで吸収しています。例えば、OSごとに (コンソールの文字) 色を変えたいとします。その場合、次のコードのように、builtin.os.tag
の列挙型でswitchし、os_color
という共通して使える定数に、各OSで使う色を代入しておきます。
examples/ch04-basic-syntax/src/switch_enum.zen:23:27
const os_color = switch(builtin.os.tag) {
.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.tag) {
.macosx => ok(os_color == .Red),
.linux => ok(os_color == .Green),
else => ok(os_color == .Blue),
}
}
タグ付き共用体に対してswitchを利用することができます。このことにより、異なるデータ構造をswitch
で容易に取り扱うことができます。非常に強力な機能なので、詳しく見ていきましょう。
IPv4 (8ビット×4) アドレスとIPv6 (16ビット×8) アドレスとを表現できるIpAddr
タグ付き共用体を考えます。IpAddr
はヴァリアントのipv4
とipv6
とを持ちます。それぞれのヴァリアントをIPv4とIPv6の作法に則って表示するprintAddress
関数があります。
examples/ch04-basic-syntax/src/switch_tagged_union.zen:4:25
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
を使用してipv4
とipv6
のヴァリアントをパターンマッチしています。self
で受け取ったIpAddr
の値がipv4
ヴァリアントであればIPv4の形式で、ipv6
のヴァリアントであればIPv6の形式でアドレスが表示されます。
examples/ch04-basic-syntax/src/switch_tagged_union.zen:27:37
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-2020 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.