列挙型 (enum) は、一連の整数にタグを与えるための仕組みです。
列挙型の定義は、const 型名 = enum { タグ名, ... };
で行います。
examples/ch03-user-defined-types/src/enum.zen:4:8
const State = enum {
Wait,
Ready,
Running,
};
列挙型の値は、通常の変数定義と同じように定義できます。
examples/ch03-user-defined-types/src/enum.zen:10:15
test "define an enum variant" {
const s = State.Running;
ok(s == State.Running);
ok(s != State.Wait);
}
同じ列挙型同士は一致比較できます。異なる列挙型同士や、列挙型と整数の比較はできません。
型推論可能な場所で列挙型リテラルを使用する場合、列挙型名を省略した.タグ名
の省略記法が利用可能です。
// 以下の2行は同等
ok(s == State.Running);
ok(s == .Running);
デフォルトでは、列挙型のタグには、0から順番に整数値が割り当てられます。タグを整数型に変換するには、組込み関数の@enumToInt
を使用します。
examples/ch03-user-defined-types/src/enum.zen:17:21
test "enum to int" {
ok(@enumToInt(State.Wait) == 0);
ok(@enumToInt(State.Ready) == 1);
ok(@enumToInt(State.Running) == 2);
}
逆に、整数値から列挙型のタグに変換するには、組込み関数の@intToEnum
を使用します。
examples/ch03-user-defined-types/src/enum.zen:23:25
test "int to enum" {
ok(@intToEnum(State, 2) == State.Running);
}
タグに割り振る整数値を設定することもできます。
examples/ch03-user-defined-types/src/enum.zen:27:35
const Number = enum {
One = 1,
Two = 2,
Three = 3,
};
test "override enum number" {
ok(@enumToInt(Number.One) == 1);
}
デフォルトでは、Zenは列挙型に対して、タグの個数を表現可能な最小限ビット幅の符号なし整数型を割り当てます。この整数型を列挙型のタグ型と呼びます。上の列挙型Number
のタグ型はu2
になります。
examples/ch03-user-defined-types/src/enum.zen:37:39
test "bit-width of enum type" {
ok(@TagType(Number) == u2);
}
そのため、表現に3ビット必要な整数4
を割り当てると、コンパイルエラーになります。
const Number = enum {
One = 1,
Two = 2,
Three = 3,
Four = 4,
};
error[E02047]: unable to convert '4' to 'u2'
Four = 4,
~
列挙型のタグ型を設定することもできます。enum(タグ型)
でタグ型を設定します。
examples/ch03-user-defined-types/src/enum.zen:41:50
const BigNumbers = enum(u32) {
KILO = 1_000,
MEGA = 1_000_000,
GIGA = 1_000_000_000,
};
test "specify tag type" {
ok(@TagType(BigNumbers) == u32);
ok(@enumToInt(BigNumbers.GIGA) == 1_000_000_000);
}
列挙型はswitch
でそれぞれのタグを場合分けすることができます。
次の曜日を表す列挙型を例に説明します。
examples/ch03-user-defined-types/src/enum.zen:52:60
const DayOfWeek = enum {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
};
列挙型をswitch
で取り扱う構文は、以下の通りです。
switch (列挙型の値) {
タグ1 => タグ1に対する処理,
タグ2 => タグ2に対する処理,
...,
else => その他の場合に対する処理,
}
ノート:
else => その他の場合に対する処理,
の分岐は、列挙型のタグに対する分岐が網羅されていれば、省略可能です。
Zenのswitch
は式であるため、値を返すことができます。その曜日が平日かどうかをブール型で返す処理は、次のように書くことができます (ただしこの書き方は冗長です) 。
examples/ch03-user-defined-types/src/enum.zen:62:72
fn isWeekday(day: DayOfWeek) bool {
return switch (day) {
.Monday => true,
.Tuesday => true,
.Wednesday => true,
.Thursday => true,
.Friday => true,
.Saturday => false,
.Sunday => false,
};
}
列挙型に対するswitch
は、必ず全てのヴァリアントに対する場合分けを書かねばなりません。上のswitch
式から、Sunday
の場合を消した場合、次のコンパイルエラーになります。
error: enumeration value 'src.enum_define.DayOfWeek.Sunday' not handled in switch
return switch (day) {
^
switch
式の詳細な利用方法については、4.5 パターンマッチで説明します。
列挙型には関数およびメソッドを定義することができます。
関数は列挙型名前空間内の関数として扱われ、列挙型.関数名
で呼び出すことができます。
examples/ch03-user-defined-types/src/enum.zen:78:99
const Os = enum {
Linux,
Windows,
MacOS,
FreeBSD,
fn allOs() []u8 {
return "Linux, Windows, macOS, FreeBSD"[0..];
}
fn isXnix(self: Os) bool {
return switch (self) {
.Linux, .FreeBSD => true,
else => false,
};
}
};
const equalSlices = std.testing.equalSlices;
test "namespaced function in enum" {
equalSlices(u8, "Linux, Windows, macOS, FreeBSD", Os.allOs());
}
メソッドは第一引数に列挙型の値 (またはそのポインタ) を受け取る関数です。メソッドは、変数名.メソッド名
で呼び出すことができます。
examples/ch03-user-defined-types/src/enum.zen:101:103
test "method" {
const linux = Os.Linux;
ok(linux.isXnix() == true);
};
メソッドは次のように呼び出すこともできます。
examples/ch03-user-defined-types/src/enum.zen:102:105
const linux = Os.Linux;
// 次の3行は同等です。
ok(linux.isXnix() == true);
ok(Os.Linux.isXnix() == true);
ok(Os.isXnix(Os.Linux) == true);
Zenのenum
はC言語との互換性を保証していません。
次のコードは、C言語と互換性のある c_function
にZenの Color
の値を渡そうとしています。
const Color = enum {
Green,
Yellow,
Red,
};
export fn c_function(color: Color) void {
// do something
}
test "give a enum value to C function" {
c_function(Color.Red);
}
このコードをコンパイルすると、次のコンパイルエラーになります。
error[E09083]: 'Color' cannot be a parameter for function with calling convention 'C'
export fn c_function(color: Color) void {
~
C言語と相互に列挙型をやり取りしたい場合、 extern enum
を使用します。
examples/ch03-user-defined-types/src/extern_enum.zen:4:17
const Color = extern enum {
// ^^^^^^
Green,
Yellow,
Red,
};
このように、 extern
を指定した列挙型は、C言語との相互運用に利用できます。
非網羅的な列挙型は、列挙型の値が定義したタグ以外の整数値を取りうることを明示します。
列挙型の最後のフィールドに _
を定義すると、その列挙型は非網羅的な列挙型になります。
非網羅的な列挙型を定義する場合、タグ型を指定する必要があります。
examples/ch03-user-defined-types/src/non_exhaustive_enum.zen:23:28
const LessThanThree = enum(u2) {
Zero = 0,
One = 1,
Two = 2,
_,
};
非網羅的な列挙型に対する switch
では、全てのタグに対する分岐を網羅した上で、 _
の分岐を書かないとコンパイルエラーになります。
examples/ch03-user-defined-types/src/non_exhaustive_enum.zen:30:42
fn processLessThanThree(v: LessThanThree) void {
switch (v) {
.Zero => std.debug.warn("zero!\n", .{}),
.One => std.debug.warn("one!\n", .{}),
.Two => std.debug.warn("two!\n", .{}),
// `_` の分岐が必要です
_ => std.debug.warn("something went wrong!!\n", .{}),
}
}
test "non-exhaustive enum" {
var v = LessThanThree.One;
processLessThanThree(v);
}
ただし、else
の分岐が書かれている場合、 _
の分岐は不要です。
switch (v) {
.Zero => std.debug.warn("zero!\n", .{}),
.One => std.debug.warn("one!\n", .{}),
else => std.debug.warn("two or more!\n", .{}),
// `_` の分岐は不要です
}
なぜ非網羅的な列挙型が必要なのでしょうか? それは、Zen言語の外の世界で発生した何らかの間違いに、正しく対処するためです。
通常、Zenの列挙型は、その列挙型で定義されている値しか取らないことを意味します。
次の列挙型を定義すると、 LessThanThree
の (実際の) 値は、 0
, 1
, 2
のいずれかであることを通常は期待できます。
const LessThanThree = enum {
Zero = 0,
One = 1,
Two = 2,
};
LessThanThree
の値を処理する switch
は、定義している値を網羅すれば、網羅的に値を処理できます。
fn processLessThanThree(v: LessThanThree) void {
switch (v) {
.Zero => std.debug.warn("zero!\n", .{}),
.One => std.debug.warn("one!\n", .{}),
.Two => std.debug.warn("two!\n", .{}),
}
}
test "exhaustive enum" {
var v = LessThanThree.One;
processLessThanThree(v);
}
強調しますが、通常は、これで十分です。
ここで、LessThanThree
の実体を詳細に考えてみます。
LessThanThree
のタグ型は、u2
になります (コンパイラが自動的にそうします) 。
明示的に書くと、次のコードの通りです。
const LessThanThree = enum(u2) {
// ^^
Zero = 0,
One = 1,
Two = 2,
};
u2
は 0
から 3
の値を表現できるため、何らかの間違いが発生すると、 3
の整数値を取る可能性があります。
ここで、何らかの間違いとは、C言語との相互運用で想定しない整数値が入っている場合や、宇宙線によるソフトエラーでビットが反転するような事態です。
そのような場合に備えて、列挙型で定義されている値を網羅した上で、else
を書くことができます。
switch (v) {
.Zero => std.debug.warn("zero!\n", .{}),
.One => std.debug.warn("one!\n", .{}),
.Two => std.debug.warn("two!\n", .{}),
// you can add `else`
else => @panic("something went wrong", .{}),
}
しかし、通常の列挙型では else
を書かなくてもコンパイルできます。
そこで、Zen言語の外の世界で発生した何らかの間違いへの対処を強制するために、非網羅的な列挙型を利用します。
列挙型に関連する組込み関数を、次の列挙型を例に紹介します。
examples/ch03-user-defined-types/src/enum.zen:110:115
const Value = enum {
Zero,
One,
Two,
Three,
};
列挙型の値を整数値に変換します。
examples/ch03-user-defined-types/src/enum.zen:115:117
test "@enumToInt" {
ok(@enumToInt(Value.Zero) == 0);
}
整数値を列挙型の値に変換します。
examples/ch03-user-defined-types/src/enum.zen:119:121
test "@intToEnum" {
ok(@intToEnum(Value, 0) == .Zero);
}
列挙型のタグ型 (列挙型に割り当てられた整数型) を取得します。
examples/ch03-user-defined-types/src/enum.zen:123:125
test "@TagType" {
ok(@TagType(Value) == u2);
}
列挙型のタグ名を、列挙型の値から取得します。
examples/ch03-user-defined-types/src/enum.zen:127:129
test "@tagName" {
equalSlices(u8, "Zero", @tagName(Value.Zero));
}
型情報(builtin.TypeInfo
)を取得します。この情報からタグの数やタグ名を取得できます。
examples/ch03-user-defined-types/src/enum.zen:131:138
test "@typeInfo" {
ok(@typeInfo(Value).Enum.fields.len == 4);
equalSlices(u8, "Zero", @typeInfo(Value).Enum.fields[0].name);
equalSlices(u8, "One", @typeInfo(Value).Enum.fields[1].name);
equalSlices(u8, "Two", @typeInfo(Value).Enum.fields[2].name);
equalSlices(u8, "Three", @typeInfo(Value).Enum.fields[3].name);
}
@memberCount
,@memberName
は削除されました。
☰ 人の生きた証は永遠に残るよう ☰
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.