評価11: コンパイル時計算 - 評価基準

評価の目的

この評価では、Zigの最も強力な機能の一つであるコンパイル時計算(comptime)の理解度と実装力を確認します。comptime変数、関数、型生成、リフレクション、コンパイル時最適化など、メタプログラミングの幅広い側面をカバーします。

学習目標

この評価を通じて、以下の能力を確認します:

  • コンパイル時に実行される計算の実装
  • ジェネリック型とパラメータ多相の理解
  • 型リフレクションとメタプログラミング
  • コンパイル時最適化によるパフォーマンス向上
  • 型安全なコード生成

評価項目

1. コンパイル時ルックアップテーブル(20点)

項目 配点 評価基準
フィボナッチ数列生成 5点 comptime関数で正しく生成され、実行時計算が不要
階乗テーブル生成 5点 オーバーフローを考慮し、正しい値が生成される
素数リスト生成 5点 エラトステネスの篩などで効率的に素数を生成
べき乗テーブル生成 5点 任意の基数でべき乗テーブルが正しく生成される

確認ポイント:

  • [ ] すべてのテーブルがコンパイル時に生成されている
  • [ ] comptime キーワードが適切に使用されている
  • [ ] 計算結果が数学的に正しい
  • [ ] ルックアップが実行時に高速にアクセス可能

コード品質チェック:

// 良い例:コンパイル時に完全に評価される
fn generateFibonacci(comptime n: usize) [n]u64 {
    comptime {
        var result: [n]u64 = undefined;
        result[0] = 0;
        result[1] = 1;
        var i = 2;
        while (i < n) : (i += 1) {
            result[i] = result[i - 1] + result[i - 2];
        }
        return result;
    }
}

// 悪い例:実行時計算が残っている
fn generateFibonacci(n: usize) []u64 {  // comptime なし
    // ランタイムで計算される
}

2. ジェネリック型生成(20点)

項目 配点 評価基準
Pair型の実装 5点 異なる型のペアを扱え、swap/map操作が正しい
Array型の実装 5点 固定長配列でmap/filter操作が正しく動作
Option型の実装 5点 Some/Noneの扱いが適切で、関数型操作が実装されている
型パラメータの活用 5点 複数の型パラメータを適切に扱っている

確認ポイント:

  • [ ] 型パラメータが comptime で指定されている
  • [ ] ジェネリック型が再利用可能
  • [ ] 型安全性が保たれている
  • [ ] エラーハンドリングが適切

実装例の評価:

// 優れた実装:型安全でエラーハンドリングも適切
fn Option(comptime T: type) type {
    return union(enum) {
        Some: T,
        None,

        pub fn unwrap(self: @This()) T {
            return switch (self) {
                .Some => |value| value,
                .None => @panic("called unwrap on None"),
            };
        }

        pub fn unwrapOr(self: @This(), default: T) T {
            return switch (self) {
                .Some => |value| value,
                .None => default,
            };
        }
    };
}

// 不十分な実装:エラーケースの考慮が不足
fn Option(comptime T: type) type {
    return struct {
        value: T,  // Noneを表現できない
    };
}

3. コンパイル時リフレクション(20点)

項目 配点 評価基準
フィールド情報取得 6点 fieldCount、fieldNames、fieldTypesが正しく動作
フィールド存在確認 6点 hasField、hasFieldOfTypeが正しく実装されている
構造体情報表示 4点 サイズ、アライメント、フィールド詳細が正しく表示
列挙型の扱い 4点 enumValuesで全ての値を取得できる

確認ポイント:

  • [ ] std.meta.fields を正しく使用
  • [ ] inline for でフィールドを反復処理
  • [ ] 型情報が実行時にアクセス可能
  • [ ] コンパイルエラーが適切に発生

評価例:

// 完全な実装
fn fieldCount(comptime T: type) usize {
    const fields = std.meta.fields(T);
    return fields.len;
}

fn hasField(comptime T: type, comptime field_name: []const u8) bool {
    const fields = std.meta.fields(T);
    inline for (fields) |field| {
        if (std.mem.eql(u8, field.name, field_name)) {
            return true;
        }
    }
    return false;
}

// 不完全:inline forを使っていない
fn hasField(comptime T: type, comptime field_name: []const u8) bool {
    const fields = std.meta.fields(T);
    for (fields) |field| {  // comptime反復が必要
        if (std.mem.eql(u8, field.name, field_name)) {
            return true;
        }
    }
    return false;
}

4. コンパイル時最適化(20点)

項目 配点 評価基準
CRC32テーブル生成 5点 256要素のテーブルがコンパイル時に生成される
ビット反転テーブル 5点 ルックアップテーブルでビット演算を最適化
ループ展開 5点 inline forで効率的にループが展開される
分岐最適化 5点 2のべき乗判定でシフト演算に最適化される

確認ポイント:

  • [ ] テーブルが静的に生成されている
  • [ ] 実行時のオーバーヘッドが最小限
  • [ ] アルゴリズムが正しく実装されている
  • [ ] パフォーマンスが向上している

最適化の評価:

// 優れた最適化:コンパイル時にテーブル生成
const crc32_table = comptime generateCRC32Table();

fn crc32(data: []const u8) u32 {
    var crc: u32 = 0xFFFFFFFF;
    for (data) |byte| {
        const index = @as(u8, @truncate((crc ^ byte) & 0xFF));
        crc = (crc >> 8) ^ crc32_table[index];  // ルックアップ
    }
    return ~crc;
}

// 非最適化:実行時に毎回計算
fn crc32(data: []const u8) u32 {
    var crc: u32 = 0xFFFFFFFF;
    for (data) |byte| {
        // テーブルなしで計算(遅い)
        var temp = crc ^ byte;
        var i: u8 = 0;
        while (i < 8) : (i += 1) {
            if (temp & 1 == 1) {
                temp = (temp >> 1) ^ 0xEDB88320;
            } else {
                temp >>= 1;
            }
        }
        crc = temp;
    }
    return ~crc;
}

5. ボーナス課題(20点)

項目 配点 評価基準
型安全ビルダーパターン 10点 フィールド設定が型安全で、検証機能が実装されている
コンパイル時JSONパーサー 10点 整数、文字列、配列の解析が正しく動作する

ボーナス評価ポイント:

  • [ ] ビルダーパターンでコンパイルエラーが適切に発生
  • [ ] JSONパーサーがコンパイル時に完全に動作
  • [ ] エラーメッセージが分かりやすい
  • [ ] 実用的な設計になっている

チェックリスト

コンパイル前の確認

  • [ ] すべての関数が comptime パラメータを適切に使用
  • [ ] 型パラメータが comptime type で宣言されている
  • [ ] inline for が必要な箇所で使用されている
  • [ ] コンパイルエラーメッセージが適切

実行時の確認

  • [ ] すべてのルックアップテーブルが正しい値を持つ
  • [ ] ジェネリック型が異なる型で動作する
  • [ ] リフレクション情報が正確に表示される
  • [ ] 最適化により高速に動作する

コード品質の確認

  • [ ] コードが読みやすく、適切にコメントされている
  • [ ] エラーケースが適切に処理されている
  • [ ] メモリリークがない
  • [ ] テストケースが十分にカバーされている
  • 合格基準

    レベル 点数 説明
    優秀 90-100 すべての機能を完璧に実装し、ボーナス課題も完了
    良好 80-89 マンダトリー課題を完全に実装、ボーナス課題の一部完了
    合格 64-79 マンダトリー課題の80%以上を正しく実装
    要再提出 0-63 マンダトリー課題の実装が不十分

    最低合格ライン: 64点以上(マンダトリー80点満点の80%)

    よくある減点ポイント

    1. comptime の誤用(-5〜10点)

    // NG: comptime が必要な場所で使っていない
    fn generateTable(size: usize) []u32 {  // comptime がない
        var table: [size]u32 = undefined;  // コンパイルエラー
        return &table;
    }
    
    // OK: comptime を使用
    fn generateTable(comptime size: usize) [size]u32 {
        comptime {
            var table: [size]u32 = undefined;
            // 初期化
            return table;
        }
    }
    

    2. 型パラメータの扱い(-5〜10点)

    // NG: type が comptime でない
    fn GenericStruct(T: type) type {  // comptime がない
        return struct {
            value: T,
        };
    }
    
    // OK: comptime type
    fn GenericStruct(comptime T: type) type {
        return struct {
            value: T,
        };
    }
    

    3. リフレクションの誤用(-5点)

    // NG: inline for を使っていない
    fn printFields(comptime T: type) void {
        const fields = std.meta.fields(T);
        for (fields) |field| {  // コンパイルエラー
            std.debug.print("{s}\n", .{field.name});
        }
    }
    
    // OK: inline for を使用
    fn printFields(comptime T: type) void {
        const fields = std.meta.fields(T);
        inline for (fields) |field| {
            std.debug.print("{s}\n", .{field.name});
        }
    }
    

    4. 最適化の不足(-5〜10点)

  • ルックアップテーブルを使わず実行時計算している
  • ループ展開の機会を逃している
  • 分岐予測を考慮していない
  • 学習の手引き

    comptime の理解を深める

  • 基礎から始める
- comptime変数と通常の変数の違いを理解する - コンパイル時と実行時の境界を明確にする

  • 型生成の練習
- 単純なジェネリック型から始める - 複数の型パラメータを扱う練習をする

  • リフレクションの活用
- std.meta の各機能を試す - 実際のユースケースで応用する

  • 最適化の実践
- コンパイル時計算のメリットを測定する - ベンチマークで効果を確認する

効果的な学習方法

  • 段階的アプローチ
- まず簡単な例で動作を確認 - 徐々に複雑な実装に挑戦

  • エラーメッセージの活用
- コンパイルエラーから学ぶ - 型システムの制約を理解する

  • 実践的な応用
- 実際のプロジェクトで使ってみる - パフォーマンス向上を体感する

ピアレビューのポイント

評価者は以下の点を確認してください:

1. 動作確認

  • すべてのテストケースが通ること
  • コンパイルが成功すること
  • 実行結果が期待通りであること

2. コード確認

  • comptime が適切に使われているか
  • 型安全性が保たれているか
  • エッジケースが考慮されているか

3. 理解度の確認

質問例:
  • 「なぜここで comptime が必要ですか?」
  • 「このコードはいつ評価されますか?」
  • 「型パラメータの制約は何ですか?」
  • フィードバックの例

    良いフィードバック

    > 「コンパイル時ルックアップテーブルの実装が素晴らしいです。CRC32のアルゴリズムが正しく、テーブルが完全にコンパイル時に生成されています。ボーナス課題の型安全ビルダーも、コンパイルエラーを適切に活用していて実用的です。次のステップとして、より複雑なメタプログラミングパターンに挑戦してみてください。」

    改善が必要な場合のフィードバック

    > 「ジェネリック型の実装は良いですが、Option型のunwrap関数でpanicではなくエラーを返すようにした方が安全です。また、リフレクション部分でinline forを使っていないため、コンパイルエラーが発生しています。inline forに変更して再提出してください。」

    参考資料

  • Zig Language Reference - Comptime: https://ziglang.org/documentation/master/#comptime
  • Ziglearn - Comptime: https://ziglearn.org/chapter-2/#comptime
  • Zig Standard Library - Meta: https://ziglang.org/documentation/master/std/#std.meta
  • Zig Community Examples: https://github.com/ziglang/zig/tree/master/lib/std

まとめ

コンパイル時計算は、Zigの最も強力で独特な機能です。この評価を通じて、型安全性、パフォーマンス、コードの再利用性を同時に達成する方法を学びました。これらの技術は、実践的なZigプログラミングにおいて非常に重要です。理解を深め、さまざまなパターンで応用できるようになりましょう。