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

列挙型 (enum)

列挙型 (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

列挙型は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);

extern enum

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,
};

u20 から 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,
};

@enumToInt

列挙型の値を整数値に変換します。

examples/ch03-user-defined-types/src/enum.zen:115:117

test "@enumToInt" {
    ok(@enumToInt(Value.Zero) == 0);
}

@intToEnum

整数値を列挙型の値に変換します。

examples/ch03-user-defined-types/src/enum.zen:119:121

test "@intToEnum" {
    ok(@intToEnum(Value, 0) == .Zero);
}

@TagType

列挙型のタグ型 (列挙型に割り当てられた整数型) を取得します。

examples/ch03-user-defined-types/src/enum.zen:123:125

test "@TagType" {
    ok(@TagType(Value) == u2);
}

@tagName

列挙型のタグ名を、列挙型の値から取得します。

examples/ch03-user-defined-types/src/enum.zen:127:129

test "@tagName" {
    equalSlices(u8, "Zero", @tagName(Value.Zero));
}

@typeInfo

型情報(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 は削除されました。

Chapter 1

Chapter 2

Chapter 3

Chapter 4

Chapter 5

Chapter 6

Chapter 7

Chapter 8

Chapter 9

Chapter 10

Chapter 11

Chapter 12

Chapter 13

Chapter 14

Chapter 15

Appendix

Error Explanation

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