Skip to content

Instantly share code, notes, and snippets.

@tkersey
Created April 5, 2026 17:52
Show Gist options
  • Select an option

  • Save tkersey/8f44f81c9e2c77a8f6b5c0591723acdf to your computer and use it in GitHub Desktop.

Select an option

Save tkersey/8f44f81c9e2c77a8f6b5c0591723acdf to your computer and use it in GitHub Desktop.
const std = @import("std");
// ----------------------------
// Core "POLY" interface pieces
// ----------------------------
// A simple 2D position record used by several shapes.
pub const Position = struct {
x: f64,
y: f64,
};
// Our "Shape" is a tagged union over a few concrete variants.
// Add more variants freely; the visitors below will adapt.
pub const Shape = union(enum) {
Circle: struct { center: Position, radius: f64 },
Rect: struct { min: Position, max: Position },
Point: Position,
};
// A tiny, generic "map" that transforms any T -> U inside containers
// we define below, using comptime reflection to recurse without runtime vtables.
pub fn map(comptime T: type, comptime U: type, value: T, f: fn (T) U) U {
// base case: if T exactly matches the input to f, just call f
if (@typeInfo(T) == @typeInfo(@TypeOf(value))) {
return f(value);
}
// fallback — but for clarity we keep map for (T->U) direct use in containers.
return f(value);
}
// ----------------------------
// Shape utilities (no vtables)
// ----------------------------
pub fn area(s: Shape) f64 {
return switch (s) {
.Circle => |c| std.math.pi * c.radius * c.radius,
.Rect => |r| (r.max.x - r.min.x) * (r.max.y - r.min.y),
.Point => 0.0,
};
}
pub fn translate(s: Shape, dx: f64, dy: f64) Shape {
return switch (s) {
.Circle => |c| .{ .Circle = .{
.center = .{ .x = c.center.x + dx, .y = c.center.y + dy },
.radius = c.radius,
}},
.Rect => |r| .{ .Rect = .{
.min = .{ .x = r.min.x + dx, .y = r.min.y + dy },
.max = .{ .x = r.max.x + dx, .y = r.max.y + dy },
}},
.Point => |p| .{ .Point = .{ .x = p.x + dx, .y = p.y + dy } },
};
}
// ----------------------------
// Small generic containers
// ----------------------------
// A minimal forward list to hold *any* T, implemented without allocator tricks.
pub fn List(comptime T: type) type {
return struct {
const Self = @This();
head: ?*Node = null,
const Node = struct {
value: T,
next: ?*Node = null,
};
pub fn pushFront(self: *Self, arena: *std.heap.ArenaAllocator, v: T) !void {
const n = try arena.allocator().create(Node);
n.* = .{ .value = v, .next = self.head };
self.head = n;
}
pub fn iter(self: *const Self) Iterator {
return .{ .curr = self.head };
}
pub const Iterator = struct {
curr: ?*Node,
pub fn next(it: *Iterator) ?*const T {
if (it.curr) |n| {
it.curr = n.next;
return &n.value;
}
return null;
}
};
};
}
// A simple RoseTree (multi-child tree) with generic payload T.
pub fn RoseTree(comptime T: type) type {
return struct {
const Self = @This();
value: T,
children: []Self = &.{},
// Map over the tree without runtime dispatch.
pub fn mapTree(comptime U: type, self: *const Self, allocator: std.mem.Allocator, f: fn (T) U) !RoseTree(U) {
var out: RoseTree(U) = .{ .value = f(self.value), .children = &.{} };
if (self.children.len > 0) {
out.children = try allocator.alloc(RoseTree(U), self.children.len);
for (self.children, 0..) |child, i| {
out.children[i] = try child.mapTree(U, allocator, f);
}
}
return out;
}
};
}
// ----------------------------
// Reflection helpers (demo)
// ----------------------------
// Pretty-print the tag name of any union(enum) value at comptime.
pub fn tagNameOfUnion(v: anytype) []const u8 {
const T = @TypeOf(v);
const info = @typeInfo(T);
if (info == .Union and info.Union.tag_type) |_| {
return @tagName(v);
}
return "not-a-tagged-union";
}
// ----------------------------
// Tests (run: `zig test thisfile.zig`)
// ----------------------------
test "area & translate for shapes" {
const s1 = Shape{ .Circle = .{ .center = .{ .x = 0, .y = 0 }, .radius = 2 } };
const s2 = Shape{ .Rect = .{ .min = .{ .x = 0, .y = 0 }, .max = .{ .x = 3, .y = 4 } } };
const s3 = Shape{ .Point = .{ .x = 1, .y = 1 } };
try std.testing.expectApproxEqAbs(std.math.pi * 4, area(s1), 1e-9);
try std.testing.expectEqual(@as(f64, 12), area(s2));
try std.testing.expectEqual(@as(f64, 0), area(s3));
const moved = translate(s2, 1, -2);
switch (moved) {
.Rect => |r| {
try std.testing.expectEqual(@as(f64, 1), r.min.x);
try std.testing.expectEqual(@as(f64, -2), r.min.y);
try std.testing.expectEqual(@as(f64, 4), r.max.x);
try std.testing.expectEqual(@as(f64, 2), r.max.y);
},
else => unreachable,
}
}
test "List(T) push & iterate" {
var gpa = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer gpa.deinit();
var list = List(i32){ .head = null };
try list.pushFront(&gpa, 10);
try list.pushFront(&gpa, 20);
try list.pushFront(&gpa, 30);
var it = list.iter();
const a = it.next().?;
const b = it.next().?;
const c = it.next().?;
try std.testing.expectEqual(@as(i32, 30), a.*);
try std.testing.expectEqual(@as(i32, 20), b.*);
try std.testing.expectEqual(@as(i32, 10), c.*);
try std.testing.expect(it.next() == null);
}
test "RoseTree mapTree" {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const A = RoseTree(i32);
const B = RoseTree([]const u8);
var t: A = .{
.value = 1,
.children = &.{
.{ .value = 2, .children = &.{} },
.{ .value = 3, .children = &.{
.{ .value = 4, .children = &.{} },
}},
},
};
const mapped = try t.mapTree([]const u8, gpa.allocator(), struct {
pub fn f(x: i32) []const u8 {
return switch (x) {
1 => "one",
2 => "two",
3 => "three",
4 => "four",
else => "other",
};
}
}.f);
try std.testing.expect(std.mem.eql(u8, mapped.value, "one"));
try std.testing.expect(std.mem.eql(u8, mapped.children[0].value, "two"));
try std.testing.expect(std.mem.eql(u8, mapped.children[1].value, "three"));
try std.testing.expect(std.mem.eql(u8, mapped.children[1].children[0].value, "four"));
// Show off reflection helper on a tagged union:
const u = Shape{ .Point = .{ .x = 0, .y = 0 } };
try std.testing.expect(std.mem.eql(u8, tagNameOfUnion(u), "Point"));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment