Zig NEWS

Discussion on: Data driven polymorphism

Collapse
 
rutenkolk profile image
Kristin Rutenkolk

Hi, thanks for the comment.

I want to clean up the code a bit before I make a comprehensive "how?" post, but sure, I can explain whats going on now. It's just that this post already got kinda long.

In the case of protocols, the association between types and implementation function is determined by a comptime lookup of all instances of "records" where we passed the protocol as an argument. Since every file is a struct, we can give in the file where our implementations are as a compile time type and iterate over every declaration in it.

If a declaration has a matching type (being DefRecord_T(this_protocol) ), then the declaration is an implementation of the protocol and we save it in a comptime array.

// pretend that T is @This() or @import("root") or something like this.
fn find_all_implementing_records(comptime T: type, comptime proto: anytype, res: []DefRecord_T(proto)) void {
    const decls = @typeInfo(T).Struct.decls;
    comptime var i: usize = 0;
    for (decls) |decl| {
        std.debug.assert(@TypeOf(decl) == std.builtin.Type.Declaration);

        comptime var declref = @field(T, decl.name);
        comptime var implements_proto = implements_protocol(declref, proto);

        if (implements_proto) {
            res[i] = declref;
            i = i + 1;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The basic Idea now is: If the protocol is called with an argument of type Arg_T, go through the array of implementations at compile time and determine the index of an implementation with dispatch_type == Arg_T. From that, get the function pointer to the implementation function. This can all happen at comptime.

The only thing that happens at runtime is to call the appropriate implementation function.

The static case is 100% comptime.

            comptime var fn_val = res[type_index].impl_fn.to_typed_fn(x_type, self);
            return fn_val(x);
Enter fullscreen mode Exit fullscreen mode

Multimethods work similarly right now, but the equal checks for the dispatch values are simply an unrolled for loop of the comptime implementations right now. This is certainly something that needs to improve, but it works as a first proof of concept.

Also... the equality checks are really hacky right now. Maybe you asked yourself how did I manage to make the multimethods work with Tuples... Well, there isn't any generic structural equals in zig, is there? std.meta.eql doesn't recursively look into slices for example, right? Soooo, right now I'm actually using std.testing.expectEqualDeep, which does exactly that. But it definitely needs to be replaced before I feel remotely comfortable with the state the code is in.

Collapse
 
maldus512 profile image
Mattia Maldini

Uh, quite cool. Some details are still hazy but now I get the gist of it. Thanks!
Looking forward a more detailed explanation!