Did I just implemented some kind of Pattern Mathing?

It’s not a real project, but I was playing with comptime trying to push the limit and this code just works I’m surprised this works actually but it does.

const std = @import("std");
const Type = std.builtin.Type;

fn last(comptime h: [:0]const u8, comptime n: u8) [:0]const u8 {
    @setEvalBranchQuota(2000000);
    const index = std.mem.lastIndexOf(u8, h, &.{n}) orelse return h;
    return h[index..][1..];
}

pub fn Match(comptime Types: []const type) type {
    const Ti = std.math.IntFittingRange(0, Types.len);
    @setEvalBranchQuota(2000000);
    return @Type(
        .{
            .@"union" = .{
                .layout = .auto,
                .tag_type = blk: {
                    break :blk @Type(
                        .{
                            .@"enum" = .{
                                .tag_type = std.math.IntFittingRange(0, Types.len),
                                .fields = fields: {
                                    var f: [Types.len]Type.EnumField = undefined;

                                    for (Types, 0..) |t, i| {
                                        f[i] = switch (@typeInfo(@TypeOf(t))) {
                                            .type => .{
                                                .name = last(@typeName(t), '.'),
                                                .value = @as(Ti, @truncate(i)),
                                            },
                                            else => .{
                                                .name = @typeName(t),
                                                .value = @as(Ti, @truncate(i)),
                                            },
                                        };
                                    }
                                    break :fields &f;
                                },
                                .decls = &.{},
                                .is_exhaustive = true,
                            },
                        },
                    );
                },
                .fields = blk: {
                    var f: [Types.len]Type.UnionField = undefined;

                    for (Types, 0..) |t, i| {
                        f[i] = switch (@typeInfo(@TypeOf(t))) {
                            .type => .{
                                .name = last(@typeName(t), '.'),
                                .type = t,
                                .alignment = @alignOf(t),
                            },
                            else => .{
                                .name = @typeName(t),
                                .type = t,
                                .alignment = @alignOf(t),
                            },
                        };
                    }
                    break :blk &f;
                },
                .decls = &.{},
            },
        },
    );
}

pub const Name = struct {
    name: []const u8 = "hi",
};

fn bar(x: f32) Match(&.{ f32, Name, i32 }) {
    // zig fmt: off
    if (x == 1) {
        return .{.f32 = x};
    } else if (x > 2) {
        return .{.Name = .{}};
    } else if (x == 3) {
        return .{.i32 = 5};
    }
    else return .{.Name = .{.name = "coucou"}};
}

test "u" {
    std.debug.print("{}", .{@TypeOf(Name)});
    for (std.meta.fieldNames(Match(&.{f32, Name, i32}))) |s| {
        std.debug.print("{s}\n", .{s});
    }


    for (0..3) |i| {
        const res = bar(@floatFromInt(i));
        switch (res) {
            .f32 => |f| std.debug.print("{d:.2}\n", .{f}),
            .Name => |s| std.debug.print("{s}\n", .{s.name}),
            .i32 => |d| std.debug.print("{d}\n", .{d}),
        }
    
    }

}

I don’t know if there is a use case, if its any useful or not, but it’s pretty cool nonetheless that you can achieve that with the language.

In most cases it would be better to just make a tagged union by hand, however I think it could be useful when doing some weird meta stuff

1 Like

I don’t see how this has anything to do with pattern matching. This is basically a variadic Either type – a sum equivalent of std.meta.Tuple. It theoretically be added to std.meta for completeness sake but I don’t think an ad-hoc union of >2 types has all that many uses.

1 Like

ahah yeah i figured still pretty fun but yeah not super useful :slight_smile:

I don’t plan on using it ever, but it’s funny that it’s possible at all

thinking about it again not sure if this could be called pattern matching

It could be used for a very crude version of function overloading, with callers declaring explicitly which version of the function they’re invoking:

foo(.{ .i32 = 42 });
foo(.{ .f32 = std.math.pi });
foo(.{ .u8 = 'A' });

But since Zig supports both explicit type arguments and duck typing, there are already idiomatic ways of defining “overloaded” functions:

fn foo(comptime T: type, arg: T) void {}
fn foo(arg: anytype) {}
1 Like

It’s a bit of a cheat for sure I’m kinda stretching it here ahah

1 Like

yes most certainly it’s not practical compared to using an alternative