課題11: コンパイル時計算の実践
マンダトリー要件 (80点)
Part 1: コンパイル時ルックアップテーブル (20点)
コンパイル時に計算されるルックアップテーブルを実装してください。
ファイル: part1/lookup_tables.zig
const std = @import("std");
// TODO: コンパイル時にフィボナッチ数列を生成
fn generateFibonacci(comptime n: usize) [n]u64 {
// 実装
}
// TODO: コンパイル時に階乗のテーブルを生成
fn generateFactorials(comptime n: usize) [n]u64 {
// 実装
}
// TODO: コンパイル時に素数のリストを生成
fn generatePrimes(comptime max: u32) []const u32 {
comptime {
var primes: [max]u32 = undefined;
var count: usize = 0;
// 実装
return primes[0..count];
}
}
// TODO: コンパイル時にべき乗のテーブルを生成
fn generatePowerTable(comptime base: u32, comptime count: usize) [count]u64 {
// 実装: base^0, base^1, base^2, ...
}
pub fn main() void {
std.debug.print("=== Lookup Tables ===\n\n", .{});
// フィボナッチ数列
comptime var fibs = generateFibonacci(15);
std.debug.print("Fibonacci numbers:\n", .{});
for (fibs, 0..) |fib, i| {
std.debug.print(" F({}) = {}\n", .{i, fib});
}
// 階乗
comptime var facts = generateFactorials(10);
std.debug.print("\nFactorials:\n", .{});
for (facts, 0..) |fact, i| {
std.debug.print(" {}! = {}\n", .{i, fact});
}
// 素数
comptime var primes = generatePrimes(50);
std.debug.print("\nPrimes up to 50:\n ", .{});
for (primes) |prime| {
std.debug.print("{} ", .{prime});
}
std.debug.print("\n", .{});
// べき乗テーブル
comptime var powers = generatePowerTable(2, 16);
std.debug.print("\nPowers of 2:\n", .{});
for (powers, 0..) |power, i| {
std.debug.print(" 2^{} = {}\n", .{i, power});
}
}
Part 2: ジェネリック型生成 (20点)
comptime型パラメータを使って汎用的なデータ構造を実装してください。
ファイル: part2/generic_types.zig
const std = @import("std");
// TODO: ジェネリックなPair型
fn Pair(comptime T: type, comptime U: type) type {
return struct {
first: T,
second: U,
const Self = @This();
pub fn init(first: T, second: U) Self {
// 実装
}
pub fn swap(self: Self) Pair(U, T) {
// 実装
}
pub fn map(
self: Self,
comptime R: type,
comptime S: type,
f: fn(T) R,
g: fn(U) S,
) Pair(R, S) {
// 実装
}
};
}
// TODO: ジェネリックなTuple型(可変長)
fn Tuple(comptime types: []const type) type {
return struct {
// 実装のヒント: @Typeを使って動的にフィールドを生成
};
}
// TODO: ジェネリックなArray型(固定長)
fn Array(comptime T: type, comptime size: usize) type {
return struct {
data: [size]T,
const Self = @This();
pub fn init(default_value: T) Self {
// 実装
}
pub fn get(self: Self, index: usize) ?T {
// 実装
}
pub fn set(self: *Self, index: usize, value: T) bool {
// 実装
}
pub fn map(self: Self, comptime R: type, func: fn(T) R) Array(R, size) {
// 実装
}
pub fn filter(self: Self, predicate: fn(T) bool) []const T {
// 実装のヒント: comptime配列を使用
}
};
}
// TODO: ジェネリックなOption型
fn Option(comptime T: type) type {
return union(enum) {
Some: T,
None,
const Self = @This();
pub fn isSome(self: Self) bool {
// 実装
}
pub fn isNone(self: Self) bool {
// 実装
}
pub fn unwrap(self: Self) T {
// 実装
}
pub fn unwrapOr(self: Self, default: T) T {
// 実装
}
pub fn map(self: Self, comptime R: type, func: fn(T) R) Option(R) {
// 実装
}
pub fn andThen(self: Self, comptime R: type, func: fn(T) Option(R)) Option(R) {
// 実装
}
};
}
pub fn main() void {
std.debug.print("=== Generic Types ===\n\n", .{});
// Pair
const pair = Pair(i32, []const u8).init(42, "answer");
std.debug.print("Pair: ({}, {s})\n", .{pair.first, pair.second});
// Array
var arr = Array(i32, 5).init(0);
_ = arr.set(0, 10);
_ = arr.set(1, 20);
_ = arr.set(2, 30);
std.debug.print("\nArray[1]: {?}\n", .{arr.get(1)});
// Option
const some = Option(i32){ .Some = 42 };
const none = Option(i32).None;
std.debug.print("\nOption some: {}\n", .{some.unwrapOr(0)});
std.debug.print("Option none: {}\n", .{none.unwrapOr(0)});
}
Part 3: コンパイル時リフレクション (20点)
型情報を使ったメタプログラミングを実装してください。
ファイル: part3/reflection.zig
const std = @import("std");
const Person = struct {
name: []const u8,
age: u32,
email: []const u8,
active: bool,
};
// TODO: 構造体のフィールド数を取得
fn fieldCount(comptime T: type) usize {
// 実装
}
// TODO: 構造体のフィールド名を配列で取得
fn fieldNames(comptime T: type) []const []const u8 {
comptime {
const fields = std.meta.fields(T);
var names: [fields.len][]const u8 = undefined;
// 実装
return &names;
}
}
// TODO: 構造体のフィールド型を配列で取得
fn fieldTypes(comptime T: type) []const type {
comptime {
const fields = std.meta.fields(T);
var types: [fields.len]type = undefined;
// 実装
return &types;
}
}
// TODO: 特定のフィールドが存在するか確認
fn hasField(comptime T: type, comptime field_name: []const u8) bool {
// 実装
}
// TODO: 特定の型のフィールドを持つか確認
fn hasFieldOfType(comptime T: type, comptime FieldType: type) bool {
// 実装
}
// TODO: 構造体の情報を表示
fn printStructInfo(comptime T: type) void {
std.debug.print("Struct: {}\n", .{T});
std.debug.print("Size: {} bytes\n", .{@sizeOf(T)});
std.debug.print("Alignment: {} bytes\n", .{@alignOf(T)});
std.debug.print("Fields: {}\n", .{fieldCount(T)});
const fields = std.meta.fields(T);
inline for (fields) |field| {
std.debug.print(" - {s}: {} ({} bytes)\n",
.{field.name, field.type, @sizeOf(field.type)});
}
}
// TODO: 列挙型の値を配列で取得
fn enumValues(comptime T: type) []const T {
comptime {
const info = @typeInfo(T);
if (info != .Enum) {
@compileError("enumValues requires enum type");
}
var values: [info.Enum.fields.len]T = undefined;
// 実装
return &values;
}
}
pub fn main() void {
std.debug.print("=== Reflection ===\n\n", .{});
printStructInfo(Person);
std.debug.print("\nField count: {}\n", .{fieldCount(Person)});
std.debug.print("Has 'name' field: {}\n", .{hasField(Person, "name")});
std.debug.print("Has 'address' field: {}\n", .{hasField(Person, "address")});
std.debug.print("Has u32 field: {}\n", .{hasFieldOfType(Person, u32)});
}
Part 4: コンパイル時最適化 (20点)
comptimeを使ってパフォーマンスを最適化する実装をしてください。
ファイル: part4/optimization.zig
const std = @import("std");
// TODO: コンパイル時にCRC32テーブルを生成
fn generateCRC32Table() [256]u32 {
comptime {
var table: [256]u32 = undefined;
var i: usize = 0;
while (i < 256) : (i += 1) {
var crc: u32 = @intCast(i);
var j: usize = 0;
while (j < 8) : (j += 1) {
if (crc & 1 == 1) {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
}
table[i] = crc;
}
return table;
}
}
const crc32_table = generateCRC32Table();
// TODO: CRC32を計算
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;
}
// TODO: コンパイル時にビット反転テーブルを生成
fn generateBitReverseTable() [256]u8 {
comptime {
var table: [256]u8 = undefined;
// 実装: 各バイトのビットを反転
return table;
}
}
// TODO: ループ展開を使った最適化
fn sumArrayUnrolled(comptime size: usize, array: [size]i32) i32 {
var sum: i32 = 0;
// 実装のヒント: inline forを使用
return sum;
}
// TODO: コンパイル時分岐による最適化
fn optimizedMultiply(comptime factor: i32, value: i32) i32 {
// 実装のヒント: factorが2のべき乗ならシフト演算を使用
if (comptime std.math.isPowerOfTwo(factor)) {
const shift = comptime std.math.log2_int(u32, @abs(factor));
return value << shift;
} else {
return value * factor;
}
}
pub fn main() void {
std.debug.print("=== Compile-time Optimization ===\n\n", .{});
// CRC32
const data = "Hello, World!";
const checksum = crc32(data);
std.debug.print("CRC32 of '{s}': 0x{x:0>8}\n\n", .{data, checksum});
// ループ展開
const numbers = [_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 };
const sum = sumArrayUnrolled(8, numbers);
std.debug.print("Sum (unrolled): {}\n\n", .{sum});
// 最適化された乗算
const result1 = optimizedMultiply(8, 10);
const result2 = optimizedMultiply(7, 10);
std.debug.print("10 * 8 (optimized): {}\n", .{result1});
std.debug.print("10 * 7 (normal): {}\n", .{result2});
}
ボーナス課題 (20点)
Bonus 1: 型安全なビルダーパターン (10点)
comptimeを使った型安全なビルダーパターンを実装してください。
ファイル: bonus1/builder.zig
const std = @import("std");
// TODO: 型安全なビルダーパターン
fn Builder(comptime T: type) type {
return struct {
const Self = @This();
value: T,
pub fn init() Self {
return Self{ .value = undefined };
}
pub fn set(self: Self, comptime field_name: []const u8, field_value: anytype) Self {
var new_self = self;
@field(new_self.value, field_name) = field_value;
return new_self;
}
pub fn build(self: Self) T {
// TODO: すべてのフィールドが設定されているか検証
return self.value;
}
// TODO: 必須フィールドが設定されているか確認
fn validateRequired(self: Self) bool {
_ = self;
// 実装
return true;
}
};
}
const ServerConfig = struct {
host: []const u8,
port: u16,
timeout: u32,
max_connections: u32,
ssl_enabled: bool,
};
pub fn main() void {
std.debug.print("=== Type-Safe Builder ===\n\n", .{});
const config = Builder(ServerConfig).init()
.set("host", "0.0.0.0")
.set("port", 8080)
.set("timeout", 30000)
.set("max_connections", 100)
.set("ssl_enabled", false)
.build();
std.debug.print("Server Config:\n", .{});
std.debug.print(" Host: {s}\n", .{config.host});
std.debug.print(" Port: {}\n", .{config.port});
std.debug.print(" Timeout: {}ms\n", .{config.timeout});
std.debug.print(" Max Connections: {}\n", .{config.max_connections});
std.debug.print(" SSL: {}\n", .{config.ssl_enabled});
}
Bonus 2: コンパイル時JSON パーサー (10点)
comptimeを使った簡易的なコンパイル時JSONパーサーを実装してください。
ファイル: bonus2/comptime_json.zig
const std = @import("std");
// TODO: コンパイル時JSONパーサー(簡易版)
fn parseJsonInt(comptime json: []const u8) i64 {
comptime {
var result: i64 = 0;
var negative = false;
var i: usize = 0;
// 空白をスキップ
while (i < json.len and json[i] == ' ') : (i += 1) {}
// 符号をチェック
if (i < json.len and json[i] == '-') {
negative = true;
i += 1;
}
// 数字を解析
while (i < json.len and json[i] >= '0' and json[i] <= '9') : (i += 1) {
result = result * 10 + (json[i] - '0');
}
return if (negative) -result else result;
}
}
// TODO: コンパイル時文字列パーサー
fn parseJsonString(comptime json: []const u8) []const u8 {
comptime {
// 実装: "..." から文字列を抽出
var start: usize = 0;
var end: usize = 0;
// 最初の"を見つける
while (start < json.len and json[start] != '"') : (start += 1) {}
start += 1;
// 次の"を見つける
end = start;
while (end < json.len and json[end] != '"') : (end += 1) {}
return json[start..end];
}
}
// TODO: コンパイル時配列パーサー
fn parseJsonArray(comptime json: []const u8) []const i64 {
comptime {
// 実装: [...] から配列を抽出
_ = json;
const result: [3]i64 = [_]i64{1, 2, 3};
return &result;
}
}
pub fn main() void {
std.debug.print("=== Compile-time JSON Parser ===\n\n", .{});
comptime var int_value = parseJsonInt(" -42 ");
std.debug.print("Parsed int: {}\n", .{int_value});
comptime var str_value = parseJsonString(" \"Hello, World!\" ");
std.debug.print("Parsed string: {s}\n", .{str_value});
comptime var arr_value = parseJsonArray("[1, 2, 3]");
std.debug.print("Parsed array: ", .{});
for (arr_value) |item| {
std.debug.print("{} ", .{item});
}
std.debug.print("\n", .{});
}
評価基準
| 項目 | 配点 |
|---|---|
| Part 1: コンパイル時ルックアップテーブル | 20点 |
| Part 2: ジェネリック型生成 | 20点 |
| Part 3: コンパイル時リフレクション | 20点 |
| Part 4: コンパイル時最適化 | 20点 |
| **マンダトリー合計** | **80点** |
| Bonus 1: 型安全なビルダーパターン | 10点 |
| Bonus 2: コンパイル時JSONパーサー | 10点 |
| **ボーナス合計** | **20点** |
合格基準
- マンダトリー: 64点以上で合格(80点満点の80%)
- ボーナス: 追加評価(最終成績の加算)
提出方法
exercise11/
├── part1/
│ └── lookup_tables.zig
├── part2/
│ └── generic_types.zig
├── part3/
│ └── reflection.zig
├── part4/
│ └── optimization.zig
├── bonus1/
│ └── builder.zig
└── bonus2/
└── comptime_json.zig
テスト実行
# マンダトリー
zig run part1/lookup_tables.zig
zig run part2/generic_types.zig
zig run part3/reflection.zig
zig run part4/optimization.zig
# ボーナス
zig run bonus1/builder.zig
zig run bonus2/comptime_json.zig
ヒント
comptime var x = 10;
- comptime関数:
fn fibonacci(n: u32) u32 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
- 型情報の取得:
const fields = std.meta.fields(T);
inline for (fields) |field| {
// フィールドを処理
}
- inline for:
inline for (0..10) |i| {
// ループが展開される
}
参考資料
- 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