Capturing an abstraction hierarchy amongst closely-related functors

my use-case starts with a generic function wrapper, whose distinquishing feature is that it has an unwrap method returning a function of type FT:

fn FxnWrapper(FT: type) type {
    return struct {
        fn unwrap(self: @This()) FT { return // some fxn of type FT }
    };
}

i then have another generic function wrapper, whose synthesized type has the same unwrap method implemented in a very specific manner… indeed, even the arguments to this second generic function wrapper are different:

fn FxnWrapper2(FT: type, comptime fxnid []const u8) type {
    return struct {
        const id = fxnid;
        fn unwrap(_: @This()) FT {
            const fxn = comptime // find function in some global table indexed by fxnid
            return fxn;
    }
} 

my use-case requires a way for me to “serialize/deserialize” function references across domains, relying on some comptime magic to effciently retrieve the function itself… correct me if i’m wrong, but an instance of the type returned by FxnWrapper2 consumes no space??? the fxnid is in fact encoded in the @typeName of this synthesized type…

back to my original question: is there a way to “cast” the result of FxnWrapper2(FT, "some_fxn_id") to the result type of FxnWrapper(FT) – hiding the actually identity of the underlying fxn of type FT and yet preserving the unwrap() method???

playing around with this a little more, i tried declaring the fxnid parameter to FxnWrapper2 as anytype – hoping it would “relax” the name of the synthesized return type (which it didn’t)…

my intent is the “close” or “curry” the fxnid constant within an implementation of FxnWrapper… perhaps there’s a way to have the type returned by FxnWrapper2 implement a method that returns an object of type FxnWrapper(FT)

a somewhat simpler variation of the same problem… here’s what i just tried:

const NameBox = struct {
    get: fn (@This()) []const u8,
};

fn NameBox2(comptime name: []const u8) type {
    return struct {
        const _name: []const u8 = name;
        pub fn get(_: @This()) []const u8 {
            return _name;
        }
        pub fn mkNameBox(_: @This()) NameBox {
            return NameBox{ .get = get };
        }
    };
}

var nb2 = NameBox2("foo"){};
var nb = nb2.mkNameBox();

pub fn main() void {
    std.log.debug("{s} {s}", .{ @typeName(@TypeOf(nb)), @typeName(@TypeOf(nb2)) });
}

the program itself had the following compile error:

src\main.zig:14:30: error: expected type 'fn (comptime main.NameBox) []const u8', found 'fn (main.NameBox2("foo"[0..3])) []const u8'
            return NameBox{ .get = get };
                            ~^~~~~~~~~
src\main.zig:14:30: note: non-generic function cannot cast into a generic function
src\main.zig:20:23: note: called from here
var nb = nb2.mkNameBox();
         ~~~~~~~~~~~~~^~

am i getting closer :wink:

That specific error you can fix by coercing "foo" which is a pointer to a sentinel terminated array to an []const u8. For example const str: []const u8 = "foo"; and then use str.

But then you get the error that I think is the big one here. The type of NameBox.get is a function that has @This() as its parameter and @This() is the type of NameBox. But in NameBox2.get the type of @This() is the type of NameBox2(name), so there’s an unreconcilable mismatch there I think.

fortunately, i’ve taken a different approach to my underlying design – which has proven successful…

i’m good – but perhaps we should leave the original question as “needing more investigation” and not marked it as solved…

1 Like