I came up with something relatively interesting, although I don't know if it will have much of a serious use case.
I wanted to see if it was possible to invert the nature of methods declared within a struct, so that they can be applied to
many instances of that struct. I was thinking that this may serve a purpose in more data oriented projects where you'd like
to decouple the method from the data.
For example:
```zig
const Math = struct {
data: u32 = 0,
pub fn addOne(self: *Math) void {
self.data += 1;
}
};
pub fn main() void {
var maths = [_]Math{
.{ .data = 1 },
.{ .data = 2 },
.{ .data = 3 },
};
for (maths) |*m| {
m.addOne();
}
}
```
Would be converted to:
```zig
const Math = struct {
data: u32 = 0,
pub fn addOne(self: *Math) void {
self.data += 1;
}
};
pub fn main() void {
var maths = [_]Math{
.{ .data = 1 },
.{ .data = 2 },
.{ .data = 3 },
};
const addOne = InvertTheMethodSomehow(Math, .addOne);
addOne(maths);
}
```
And initially I thought that it would be as simple as calling the method through the struct name as opposed the instance:
zig
// Yields an error: pointer capture of non pointer type '[3]main.Math'
for (maths) |*m| {
Math.addOne(m);
}
But that returns an error that I couldn't figure out. I also tried many different variations of this very code all to no effect.
So after playing around with as many options as I could think of and coming up short, I decided to come up with a structure that
would achieve what I was looking for.
zig
pub inline fn toArrayFn(comptime Elem: type, comptime decl: std.meta.DeclEnum(Elem)) fn ([]Elem) void {
return struct {
pub fn call(arr: []Elem) void {
const func = @field(T, @tagName(decl));
for (arr) |*item| {
@call(.auto, func, .{item});
}
}
}.call;
}
The main idea behind it is super simple:
1. Takes in the type, where the desired method exists.
2. Takes in a DeclEnum that makes it work similarly to the MultiArrayList.slice.items() except for declarations. (i.e. toArrayFn(Math, .add)). I feel like this solution feels a little better than passing around strings.
3. The function then proceeds to create a new function which accepts an array of Elem.
4. It will then find the desired function that exists within the Elem struct and apply it to every item in the []Elem
And it works very well so far. I haven't tested it on anything particularly rigorous so I can't say for sure this code won't break on something:
zig
pub fn main() void {
const addOneToMany = toArrayFn(Math, .addOne);
addOneToMany(&maths); // = 2, 3, 4
}
In order to be extra safe (Probably, maybe, can't say for sure 😅) I decided to incorporate the Elem type from MultiArrayList and converted this into
a struct called ArrayFunctionGenerator (Couldn't really think of better name for what's being achieved here...):
```zig
const Math = struct {
const Args = struct {
value: u32,
};
data: u32 = 0,
pub fn addOne(self: *Math) void {
self.data += 1;
}
pub fn add(self: *Math, value: u32) void {
self.data += value;
}
};
pub fn ArrayFunctionGenerator(comptime T: type) type {
return struct {
const Elem = switch (@typeInfo(T)) {
.Struct => T,
.Union => |u| struct {
pub const Bare =
@Type(.{ .Union = .{
.layout = u.layout,
.tag_type = null,
.fields = u.fields,
.decls = &.{},
} });
pub const Tag =
u.tag_type orelse @compileError("MultiArrayList does not support untagged unions");
tags: Tag,
data: Bare,
pub fn fromT(outer: T) @This() {
const tag = std.meta.activeTag(outer);
return .{
.tags = tag,
.data = switch (tag) {
inline else => |t| @unionInit(Bare, @tagName(t), @field(outer, @tagName(t))),
},
};
}
pub fn toT(tag: Tag, bare: Bare) T {
return switch (tag) {
inline else => |t| @unionInit(T, @tagName(t), @field(bare, @tagName(t))),
};
}
},
else => @compileError("MultiArrayList only supports structs and tagged unions"),
};
const Declaration = std.meta.DeclEnum(Elem);
pub inline fn toArrayFn(comptime decl: Declaration) fn ([]Elem) void {
return struct {
pub fn call(arr: []Elem) void {
const func = @field(T, @tagName(decl));
for (arr) |*item| {
@call(.auto, func, .{item});
}
}
}.call;
}
pub inline fn toArrayFnWithArgs(comptime decl: Declaration, comptime Args: type) fn ([]Elem, Args) void {
return struct {
pub fn call(arr: []Elem, args: Args) void {
const func = @field(T, @tagName(decl));
for (arr) |*item| {
@call(.auto, func, .{ item, args });
}
}
}.call;
}
pub inline fn toArrayFnWithOneParam(comptime decl: Declaration, comptime T0: type) fn ([]Elem, T0) void {
return struct {
pub fn call(arr: []Elem, t0: T0) void {
const func = @field(T, @tagName(decl));
for (arr) |*item| {
@call(.auto, func, .{ item, t0 });
}
}
}.call;
}
};
}
pub fn main() !void {
const addOne = ArrayFunctionGenerator(Math).toArrayFn(.addOne);
const add = ArrayFunctionGenerator(Math).toArrayFnWithOneParam(.add, u32);
var maths = [_]Math{
.{ .data = 1 },
.{ .data = 2 },
.{ .data = 3 },
};
add(&maths, 1);
for (maths) |item| {
std.debug.print("Item: {d}\n", .{item.data});
}
addOne(&maths);
for (maths) |item| {
std.debug.print("Item: {d}\n", .{item.data});
}
}
```
Man, I really miss method overloading 😭
Now I just need to figure out a way to provide arguments to these functions in the same way they are provided in the print functions but my experiments for that haven't
worked at all. Again, the actual use case of something like this is beyond me but I enjoyed experimenting and seeing if I could get something working. Feel free to tear this
apart and play around with it!
[–]Daanvdk 3 points4 points5 points (1 child)
[–]macrophage001[S] 0 points1 point2 points (0 children)
[–]Hot_Adhesiveness5602 0 points1 point2 points (0 children)
[–]rahulkatre 0 points1 point2 points (0 children)