diff options
| author | Nic Gaffney <gaffney_nic@protonmail.com> | 2025-10-21 00:09:07 -0500 |
|---|---|---|
| committer | Nic Gaffney <gaffney_nic@protonmail.com> | 2025-10-21 00:09:07 -0500 |
| commit | c034b4a1291a803028eef8b0950a7090ea54b047 (patch) | |
| tree | 987040745c887299e69f2c3014c44ea829d89bb3 | |
| parent | b4588b2064c3afde9497084caef6e83246b32501 (diff) | |
| download | funcz-c034b4a1291a803028eef8b0950a7090ea54b047.tar.gz | |
Currying, Combinators, and organization
| -rw-r--r-- | src/combinators.zig | 50 | ||||
| -rw-r--r-- | src/compose.zig | 28 | ||||
| -rw-r--r-- | src/curry.zig | 68 | ||||
| -rw-r--r-- | src/lib.zig | 107 | ||||
| -rw-r--r-- | src/main.zig | 26 | ||||
| -rw-r--r-- | src/map.zig | 62 | ||||
| -rw-r--r-- | src/newLib.zig | 163 | ||||
| -rw-r--r-- | src/oldLib.zig | 227 | ||||
| -rw-r--r-- | src/util.zig | 16 |
9 files changed, 635 insertions, 112 deletions
diff --git a/src/combinators.zig b/src/combinators.zig new file mode 100644 index 0000000..d78e40c --- /dev/null +++ b/src/combinators.zig @@ -0,0 +1,50 @@ +const std = @import("std"); +const typeVerify = @import("util.zig").typeVerify; + +pub fn I(x: anytype) @TypeOf(x) { + return x; +} + +pub fn K(x: anytype) fn(anytype) @TypeOf(x) { + return struct { + fn Kinner(y: anytype) @TypeOf(x) { + _=y; + return x; + } + }.Kinner; +} + +fn SHelper(comptime x: anytype) type { + return struct { + pub fn call(comptime y: anytype) type { + return struct { + pub fn call(z: anytype) @TypeOf(x(z)(y(z))) { + return x(z)(y(z)); + } + }; + } + }; +} + +pub fn S(comptime x: anytype) fn(anytype) fn(anytype) @TypeOf(blk: { + const dummy = struct { + fn d(a: anytype) @TypeOf(a) { return a; } + }.d; + break :blk x(dummy)(dummy); +}) { + const Helper = SHelper(x); + return struct { + fn inner1(y: anytype) fn(anytype) @TypeOf(blk: { + const dummy = struct { + fn d(a: anytype) @TypeOf(a) { return a; } + }.d; + break :blk x(dummy)(y(dummy)); + }) { + return struct { + fn inner2(z: anytype) @TypeOf(x(z)(y(z))) { + return Helper.call(y).call(z); + } + }.inner2; + } + }.inner1; +} diff --git a/src/compose.zig b/src/compose.zig new file mode 100644 index 0000000..61ebb20 --- /dev/null +++ b/src/compose.zig @@ -0,0 +1,28 @@ +const std = @import("std"); +const typeVerify = @import("util.zig").typeVerify; + +/// ```zig +/// (fn (fn (b) c, fn (a) b) fn (a) c) +/// ``` +/// Function composition +/// Type signature: (a -> b) -> (b -> c) -> (a -> c) +/// `outerFunc` and `innerFunc` are functions of types `b -> c` and `a -> b` respectively +/// Haskell equivalent: `outerFunc . innerFunc` +pub fn compose( + comptime outerFunc: anytype, + comptime innerFunc: anytype +) blk:{ + _=typeVerify(@TypeOf(outerFunc), .{ .@"fn" }); + _=typeVerify(@TypeOf(innerFunc), .{ .@"fn" }); + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + break :blk fn(in) out; +} { + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + return struct { + fn func(input: in) out { + return outerFunc(innerFunc(input)); + } + }.func; +} diff --git a/src/curry.zig b/src/curry.zig new file mode 100644 index 0000000..d196155 --- /dev/null +++ b/src/curry.zig @@ -0,0 +1,68 @@ +const std = @import("std"); + +/// ```zig +/// (fn (a, b, ..., n) r) fn (a) fn (b) ... fn (n) r +/// ``` +/// Function currying +/// Type signature: (a -> b -> ... -> n -> r) -> a -> b -> ... -> n -> r +/// Transforms a function taking multiple arguments into a sequence of functions each taking a single argument +/// `func` is a function of type `(a, b, ..., n) -> r` +/// Haskell equivalent: automatic currying (all functions are curried by default) +pub fn curry(func: anytype) curryTypeGetter(@TypeOf(func), @TypeOf(func), .{}) { + return curryHelper(func, .{}); +} + + +fn curriedTypeVerify(comptime T: type, comptime expected: std.builtin.Type) std.builtin.Type { + const info = @typeInfo(T); + if (@as(std.meta.Tag(std.builtin.Type), info) != @as(std.meta.Tag(std.builtin.Type), expected)) { + @compileError("Type mismatch"); + } + return info; +} + +fn curryTypeGetter(comptime func: type, comptime newfunc: type, comptime args: anytype) type { + const typeInfo = curriedTypeVerify(func, .{ .@"fn" = undefined }).@"fn"; + const newTypeInfo = curriedTypeVerify(newfunc, .{ .@"fn" = undefined }).@"fn"; + + if (typeInfo.params.len == args.len + 1) { + return fn(typeInfo.params[args.len].type.?) typeInfo.return_type.?; + } + + const nextParamType = typeInfo.params[args.len].type.?; + var buf: [64]type = undefined; + for (args, 0..) |a, i| { + buf[i] = if (@TypeOf(a) != type) @TypeOf(a) else a; + } + buf[args.len] = nextParamType; + + return fn(nextParamType) curryTypeGetter(func, @Type(.{ + .@"fn" = .{ + .calling_convention = newTypeInfo.calling_convention, + .is_generic = newTypeInfo.is_generic, + .params = newTypeInfo.params[1..], + .is_var_args = newTypeInfo.is_var_args, + .return_type = newTypeInfo.return_type, + } + }), buf[0..args.len+1].*); +} + +fn curryHelper(comptime func: anytype, args: anytype) curryTypeGetter(@TypeOf(func), @TypeOf(func), args) { + const typeInfo = curriedTypeVerify(@TypeOf(func), .{ .@"fn" = undefined }).@"fn"; + const argInfo = @typeInfo(@TypeOf(args)).@"struct"; + _=argInfo; + + const nextParamType = typeInfo.params[args.len].type.?; + + const Closure = struct { + fn funcCurry(arg: nextParamType) @typeInfo(curryTypeGetter(@TypeOf(func), @TypeOf(func), args)).@"fn".return_type.? { + if (args.len + 1 == typeInfo.params.len) { + return @call(.auto, func, args ++ .{arg}); + } + const newArgs = args ++ .{arg}; + return curryHelper(func, newArgs); + } + }; + + return Closure.funcCurry; +} diff --git a/src/lib.zig b/src/lib.zig index 0a50a80..721b2a0 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -1,102 +1,5 @@ -const std = @import("std"); -const Type = std.builtin.Type; - -fn typeVerify(T: type, expected: anytype) Type { - const expectedType = @TypeOf(expected); - const expectedTypeInfo = @typeInfo(expectedType); - if (expectedTypeInfo != .@"struct") - @compileError("Expected struct or tuple, found " ++ @typeName(expectedType)); - const realTypeInfo = @typeInfo(T); - for (expected) |e| { - if(realTypeInfo == e) return realTypeInfo; - } - for (expected) |e| - @compileError("Expected one of " ++ @tagName(e) ++ ", found " ++ @typeName(T)); - return realTypeInfo; -} - -/// ```zig -/// (fn (fn (b) c, fn (a) b) fn (a) c) -/// ``` -/// Function composition -/// Type signature: (a -> b) -> (b -> c) -> (a -> c) -/// `outerFunc` and `innerFunc` are functions of types `b -> c` and `a -> b` respectively -/// Haskell equivalent: `outerFunc . innerFunc` -pub fn compose( - comptime outerFunc: anytype, - comptime innerFunc: anytype -) blk:{ - _=typeVerify(@TypeOf(outerFunc), .{ .@"fn" }); - _=typeVerify(@TypeOf(innerFunc), .{ .@"fn" }); - const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; - const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; - break :blk fn(in) out; -} { - const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; - const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; - return struct { - fn func(input: in) out { - return outerFunc(innerFunc(input)); - } - }.func; -} - -/// ```zig -/// (fn (Allocator, fn (fn (a) b, []a) error{OutOfMemory}![]b) -/// ``` -/// Map a function onto a list of values, allocating space for the new slice -/// Type signature: `(a -> b) -> [a] -> [b]` -/// `func` is of type `a -> b`, where `items` is of type `[a]`. -/// `map` will return a slice of type `[b]` -/// Haskell equivalent: `map func items` -pub fn mapAlloc( - allocator: std.mem.Allocator, - func: anytype, - items: anytype, -) error{OutOfMemory}!blk:{ - const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); - const itemsInfo = typeVerify(@TypeOf(items), .{ .array, .pointer }); - switch (itemsInfo) { - .pointer => |p| if(p.size != .many and p.size != .slice) - @compileError("Expected pointer of size 'many' or 'slice', found " ++ @tagName(p)), - else =>{}, - } - - break :blk []funcInfo.@"fn".return_type.?; -} { - const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); - var result = try allocator.alloc(funcInfo.@"fn".return_type.?, items.len); - for(items, 0..) |item, i| - result[i] = func(item); - return result; -} - -/// ```zig -/// (fn (Allocator, fn (fn (a) b, []a, *[]b) void) -/// ``` -/// Map a function onto a list of values, using a buffer -/// Type signature: `(a -> b) -> [a] -> [b]` -/// `func` is of type `a -> b`, where `items` is of type `[a]` and `buffer` is a pointer to a value of type `[b]`. -/// Haskell equivalent: `map func items` -pub fn map( - func: anytype, - items: anytype, - buffer: anytype, -) void { - _=typeVerify(@TypeOf(func), .{ .@"fn" }); - const itemsInfo = typeVerify(@TypeOf(items), .{ .pointer, .array }); - const bufferInfo = typeVerify(@TypeOf(buffer), .{ .pointer }); - const bufferChildInfo = typeVerify(bufferInfo.pointer.child, .{ .pointer, .array }); - switch (itemsInfo) { - .pointer => |p| if(p.size != .many and p.size != .slice) - @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), - else =>{}, - } - switch (bufferChildInfo) { - .pointer => |p| if(p.size != .many and p.size != .slice) - @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), - else =>{}, - } - for (items, 0..) |item, i| - buffer.*[i] = func(item); -} +pub const map = @import("map.zig").map; +pub const mapAlloc = @import("map.zig").mapAlloc; +pub const curry = @import("curry.zig").curry; +pub const compose = @import("compose.zig").compose; +pub const combinators = @import("combinators.zig"); diff --git a/src/main.zig b/src/main.zig index 949c368..bffa1ae 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,6 @@ const std = @import("std"); const func = @import("funcz"); +const c = func.combinators; fn iter(n: i32) i32 { return n + 1; @@ -9,25 +10,30 @@ fn mul2(n: i32) i32 { return n * 2; } +fn add(n: i32, m: i32) i32 { + return n + m; +} + +fn addThenMultiply(n: i32, m: i32, q: i32) i32 { + return (n + m) * q; +} + pub fn main() !void { var gpa = std.heap.DebugAllocator(.{}){}; const allocator = gpa.allocator(); const iterThenMul2 = func.compose(mul2, iter); var items = [_]i32{ 0, 1, 2, 3, 4 }; const itemsSlice: []i32 = items[0..items.len]; - const newItems = try func.mapAlloc(allocator, iterThenMul2, itemsSlice); - defer allocator.free(newItems); - var buffer: [128]i32 = undefined; - func.map(iterThenMul2, itemsSlice, &buffer); + std.debug.print("curry(add)(4)(5) = {any}\n", .{ func.curry(add)(4)(5) }); std.debug.print("compose(mul2, iter)(5) = {d}\n", .{ iterThenMul2(5) }); std.debug.print("mapAlloc(allocator, compose(mul2, iter), []i32{{ 0, 1, 2, 3, 4 }}) = {{ ", .{}); - for(newItems) |item| { - std.debug.print("{d}, ", .{item}); - } - std.debug.print("}}\n", .{}); - std.debug.print("map(compose(mul2, iter), []i32{{ 0, 1, 2, 3, 4 }}, &buffer) = {{ ", .{}); - for(buffer[0..items.len]) |item| { + // func.map(func: anytype, items: anytype, buffer: anytype) + const buffer = try func.mapAlloc(allocator, func.curry(addThenMultiply)(1)(2), itemsSlice); + for(buffer) |item| { std.debug.print("{d}, ", .{item}); } std.debug.print("}}\n", .{}); + std.debug.print("I(5) = {any}\n", .{c.I(5)}); + std.debug.print("K(5)(7) = {any}\n", .{c.K(5)(7)}); + std.debug.print("(S K S K)(5)(7) = {any}\n", .{((c.S(c.K)(c.S)(c.K))(mul2)(func.curry(add)(3)))(7)}); } diff --git a/src/map.zig b/src/map.zig new file mode 100644 index 0000000..c532f9e --- /dev/null +++ b/src/map.zig @@ -0,0 +1,62 @@ +const std = @import("std"); +const typeVerify = @import("util.zig").typeVerify; + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a, *[]b) void) +/// ``` +/// Map a function onto a list of values, using a buffer +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]` and `buffer` is a pointer to a value of type `[b]`. +/// Haskell equivalent: `map func items` +pub fn map( + comptime func: anytype, + items: []typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn".params[0].type.?, + buffer: *[]typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn".return_type.?, +) void { + _=typeVerify(@TypeOf(func), .{ .@"fn" }); + // const itemsInfo = typeVerify(@TypeOf(items), .{ .pointer, .array }); + // const bufferInfo = typeVerify(@TypeOf(buffer), .{ .pointer }); + // const bufferChildInfo = typeVerify(bufferInfo.pointer.child, .{ .pointer, .array }); + // switch (itemsInfo) { + // .pointer => |p| if(p.size != .many and p.size != .slice) + // @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + // else =>{}, + // } + // switch (bufferChildInfo) { + // .pointer => |p| if(p.size != .many and p.size != .slice) + // @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + // else =>{}, + // } + for (items, 0..) |item, i| + buffer.*[i] = func(item); +} + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a) error{OutOfMemory}![]b) +/// ``` +/// Map a function onto a list of values, allocating space for the new slice +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]`. +/// `map` will return a slice of type `[b]` +/// Haskell equivalent: `map func items` +pub fn mapAlloc( + allocator: std.mem.Allocator, + func: anytype, + items: anytype, +) error{OutOfMemory}!blk:{ + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + const itemsInfo = typeVerify(@TypeOf(items), .{ .array, .pointer }); + switch (itemsInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found " ++ @tagName(p)), + else =>{}, + } + + break :blk []funcInfo.@"fn".return_type.?; +} { + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + var result = try allocator.alloc(funcInfo.@"fn".return_type.?, items.len); + for(items, 0..) |item, i| + result[i] = func(item); + return result; +} diff --git a/src/newLib.zig b/src/newLib.zig new file mode 100644 index 0000000..fff975a --- /dev/null +++ b/src/newLib.zig @@ -0,0 +1,163 @@ +const std = @import("std"); +const Type = std.builtin.Type; + +fn typeVerify(T: type, expected: anytype) Type { + const expectedType = @TypeOf(expected); + const expectedTypeInfo = @typeInfo(expectedType); + if (expectedTypeInfo != .@"struct") + @compileError("Expected struct or tuple, found " ++ @typeName(expectedType)); + const realTypeInfo = @typeInfo(T); + for (expected) |e| { + if(realTypeInfo == e) return realTypeInfo; + } + for (expected) |e| + @compileError("Expected one of " ++ @tagName(e) ++ ", found " ++ @typeName(T)); + return realTypeInfo; +} + +/// ```zig +/// (fn (fn (b) c, fn (a) b) fn (a) c) +/// ``` +/// Function composition +/// Type signature: (a -> b) -> (b -> c) -> (a -> c) +/// `outerFunc` and `innerFunc` are functions of types `b -> c` and `a -> b` respectively +/// Haskell equivalent: `outerFunc . innerFunc` +pub fn compose( + comptime outerFunc: anytype, + comptime innerFunc: anytype +) blk:{ + _=typeVerify(@TypeOf(outerFunc), .{ .@"fn" }); + _=typeVerify(@TypeOf(innerFunc), .{ .@"fn" }); + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + break :blk fn(in) out; +} { + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + return struct { + fn func(input: in) out { + return outerFunc(innerFunc(input)); + } + }.func; +} + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a) error{OutOfMemory}![]b) +/// ``` +/// Map a function onto a list of values, allocating space for the new slice +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]`. +/// `map` will return a slice of type `[b]` +/// Haskell equivalent: `map func items` +pub fn mapAlloc( + allocator: std.mem.Allocator, + func: anytype, + items: anytype, +) error{OutOfMemory}!blk:{ + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + const itemsInfo = typeVerify(@TypeOf(items), .{ .array, .pointer }); + switch (itemsInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found " ++ @tagName(p)), + else =>{}, + } + + break :blk []funcInfo.@"fn".return_type.?; +} { + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + var result = try allocator.alloc(funcInfo.@"fn".return_type.?, items.len); + for(items, 0..) |item, i| + result[i] = func(item); + return result; +} + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a, *[]b) void) +/// ``` +/// Map a function onto a list of values, using a buffer +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]` and `buffer` is a pointer to a value of type `[b]`. +/// Haskell equivalent: `map func items` +pub fn map( + func: anytype, + items: anytype, + buffer: anytype, +) void { + _=typeVerify(@TypeOf(func), .{ .@"fn" }); + const itemsInfo = typeVerify(@TypeOf(items), .{ .pointer, .array }); + const bufferInfo = typeVerify(@TypeOf(buffer), .{ .pointer }); + const bufferChildInfo = typeVerify(bufferInfo.pointer.child, .{ .pointer, .array }); + switch (itemsInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + else =>{}, + } + switch (bufferChildInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + else =>{}, + } + for (items, 0..) |item, i| + buffer.*[i] = func(item); +} + +pub fn curry(func: anytype) curryTypeGetter(@TypeOf(func), @TypeOf(func), .{}) { + return curryHelper(func, .{}); +} + +fn curryTypeGetter(comptime func: type, comptime newfunc: type, comptime args: anytype) type { + const typeInfo = typeVerify(func, .{ .@"fn" = undefined }).@"fn"; + const newTypeInfo = typeVerify(newfunc, .{ .@"fn" = undefined }).@"fn"; + + // Base case: if we've curried all but one parameter, return final function type + if (typeInfo.params.len == args.len + 1) { + return fn(typeInfo.params[args.len].type.?) typeInfo.return_type.?; + } + + // Recursive case: return function that takes next param and returns curried function + const nextParamType = typeInfo.params[args.len].type.?; + var buf: [64]type = undefined; + for (args, 0..) |a, i| { + buf[i] = if (@TypeOf(a) != type) @TypeOf(a) else a; + } + buf[args.len] = nextParamType; + + return fn(nextParamType) curryTypeGetter(func, @Type(.{ + .@"fn" = .{ + .calling_convention = newTypeInfo.calling_convention, + .is_generic = newTypeInfo.is_generic, + .params = newTypeInfo.params[1..], + .is_var_args = newTypeInfo.is_var_args, + .return_type = newTypeInfo.return_type, + } + }), buf[0..args.len+1].*); +} + +fn curryHelper(comptime func: anytype, args: anytype) curryTypeGetter(@TypeOf(func), @TypeOf(func), args) { + const typeInfo = typeVerify(@TypeOf(func), .{ .@"fn" = undefined }).@"fn"; + const argInfo = @typeInfo(@TypeOf(args)).@"struct"; + _=argInfo; + + const nextParamType = typeInfo.params[args.len].type.?; + + const Closure = struct { + fn funcCurry(arg: nextParamType) curryTypeGetter(@TypeOf(func), @TypeOf(func), args).ReturnType { + // Base case: if we have all arguments, call the function + if (args.len + 1 == typeInfo.params.len) { + return @call(.auto, func, args ++ .{arg}); + } + + // Recursive case: create new tuple with additional argument + const newArgs = args ++ .{arg}; + return curryHelper(func, newArgs); + } + }; + + return Closure.funcCurry; +} + +fn intToStringZ(int: u32, buf: []u8) ![:0]u8 { + return try std.fmt.bufPrintZ(buf, "{}", .{int}); +} + +// TODO: Add tests diff --git a/src/oldLib.zig b/src/oldLib.zig new file mode 100644 index 0000000..013daa7 --- /dev/null +++ b/src/oldLib.zig @@ -0,0 +1,227 @@ +const std = @import("std"); +const Type = std.builtin.Type; + +fn typeVerify(T: type, expected: anytype) Type { + const expectedType = @TypeOf(expected); + const expectedTypeInfo = @typeInfo(expectedType); + if (expectedTypeInfo != .@"struct") + @compileError("Expected struct or tuple, found " ++ @typeName(expectedType)); + const realTypeInfo = @typeInfo(T); + for (expected) |e| { + if(realTypeInfo == e) return realTypeInfo; + } + for (expected) |e| + @compileError("Expected one of " ++ @tagName(e) ++ ", found " ++ @typeName(T)); + return realTypeInfo; +} + +/// ```zig +/// (fn (fn (b) c, fn (a) b) fn (a) c) +/// ``` +/// Function composition +/// Type signature: (a -> b) -> (b -> c) -> (a -> c) +/// `outerFunc` and `innerFunc` are functions of types `b -> c` and `a -> b` respectively +/// Haskell equivalent: `outerFunc . innerFunc` +pub fn compose( + comptime outerFunc: anytype, + comptime innerFunc: anytype +) blk:{ + _=typeVerify(@TypeOf(outerFunc), .{ .@"fn" }); + _=typeVerify(@TypeOf(innerFunc), .{ .@"fn" }); + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + break :blk fn(in) out; +} { + const out = @typeInfo(@TypeOf(outerFunc)).@"fn".return_type.?; + const in = @typeInfo(@TypeOf(innerFunc)).@"fn".params[0].type.?; + return struct { + fn func(input: in) out { + return outerFunc(innerFunc(input)); + } + }.func; +} + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a) error{OutOfMemory}![]b) +/// ``` +/// Map a function onto a list of values, allocating space for the new slice +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]`. +/// `map` will return a slice of type `[b]` +/// Haskell equivalent: `map func items` +pub fn mapAlloc( + allocator: std.mem.Allocator, + func: anytype, + items: anytype, +) error{OutOfMemory}!blk:{ + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + const itemsInfo = typeVerify(@TypeOf(items), .{ .array, .pointer }); + switch (itemsInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found " ++ @tagName(p)), + else =>{}, + } + + break :blk []funcInfo.@"fn".return_type.?; +} { + const funcInfo = typeVerify(@TypeOf(func), .{ .@"fn" }); + var result = try allocator.alloc(funcInfo.@"fn".return_type.?, items.len); + for(items, 0..) |item, i| + result[i] = func(item); + return result; +} + +/// ```zig +/// (fn (Allocator, fn (fn (a) b, []a, *[]b) void) +/// ``` +/// Map a function onto a list of values, using a buffer +/// Type signature: `(a -> b) -> [a] -> [b]` +/// `func` is of type `a -> b`, where `items` is of type `[a]` and `buffer` is a pointer to a value of type `[b]`. +/// Haskell equivalent: `map func items` +pub fn map( + func: anytype, + items: anytype, + buffer: anytype, +) void { + _=typeVerify(@TypeOf(func), .{ .@"fn" }); + const itemsInfo = typeVerify(@TypeOf(items), .{ .pointer, .array }); + const bufferInfo = typeVerify(@TypeOf(buffer), .{ .pointer }); + const bufferChildInfo = typeVerify(bufferInfo.pointer.child, .{ .pointer, .array }); + switch (itemsInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + else =>{}, + } + switch (bufferChildInfo) { + .pointer => |p| if(p.size != .many and p.size != .slice) + @compileError("Expected pointer of size 'many' or 'slice', found '" ++ @tagName(p.size) ++ "'"), + else =>{}, + } + for (items, 0..) |item, i| + buffer.*[i] = func(item); +} + +pub fn curry(func: anytype) blk: { + const typeInfo = typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn"; + if (typeInfo.params.len == 1) + break :blk @TypeOf(func); + if (typeInfo.params.len == 2) + break :blk fn(typeInfo.params[0].type.?) + fn(typeInfo.params[1].type.?) typeInfo.return_type.?; + if (typeInfo.params.len == 3) + break :blk fn(typeInfo.params[0].type.?) + fn(typeInfo.params[1].type.?) + fn(typeInfo.params[2].type.?) typeInfo.return_type.?; + +} { + const typeInfo = typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn"; + if (typeInfo.params.len == 1) + return func; + if (typeInfo.params.len == 2) + return struct { + fn funct(arg1: typeInfo.params[0].type.?) fn(typeInfo.params[1].type.?) typeInfo.return_type.? { + return struct { + fn func2(arg2: typeInfo.params[1].type.?) typeInfo.return_type.? { + return func(arg1, arg2); + } + }.func2; + } + }.funct; + if (typeInfo.params.len == 3) + return struct { + fn func1(arg1: typeInfo.params[0].type.?) fn(typeInfo.params[1].type.?) fn(typeInfo.params[2].type.?) + typeInfo.return_type.? { + return struct { + fn func2(arg2: typeInfo.params[1].type.?) fn(typeInfo.params[2].type.?) typeInfo.return_type.? { + return struct { + fn func3(arg3: typeInfo.params[2].type.?) typeInfo.return_type.? { + return func(arg1, arg2, arg3); + } + }.func3; + } + }.func2; + } + }.func1; +} + +pub fn curryHelper(func: anytype, args: anytype) blk: { + const typeInfo = typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn"; + _=typeVerify(@TypeOf(args), .{ .@"struct" }); + if (typeInfo.params.len == 1) + break :blk @TypeOf(func); + const newInfo = std.builtin.Type{ + .@"fn" = .{ + .calling_convention = typeInfo.calling_convention, + .is_generic = typeInfo.is_generic, + .params = typeInfo.params[1..], + .is_var_args = typeInfo.is_var_args, + .return_type = typeInfo.return_type, + } + }; + _=newInfo; + // break :blk fn(typeInfo.params[args.len].type.?) @Type(newInfo); + break :blk type; +} { + const typeInfo = typeVerify(@TypeOf(func), .{ .@"fn" }).@"fn"; + const argInfo = typeVerify(@TypeOf(args), .{ .@"struct" }).@"struct"; + if (args.len == typeInfo.params.len) return struct { + pub fn funcCurry() typeInfo.return_type.? { + return @call(.auto, func, args); + } + }; + const newInfo = std.builtin.Type{ + .@"fn" = .{ + .calling_convention = typeInfo.calling_convention, + .is_generic = typeInfo.is_generic, + .params = typeInfo.params[1..], + .is_var_args = typeInfo.is_var_args, + .return_type = typeInfo.return_type, + } + }; + _=newInfo; + // const newType = @Type(newInfo); + return struct { + pub fn funcCurry(arg: typeInfo.params[0].type.?) type { + var fields: [64]std.builtin.Type.StructField = .{std.builtin.Type.StructField{.name="10",.type=type,.is_comptime=false,.alignment=8,.default_value_ptr=null}} ** 64; + for (argInfo.fields, 0..) |f, i| { + fields[i] = f; + } + var buf2: [3:0]u8 = undefined; + fields[args.len] = .{ + .name = blk: { + break :blk try intToStringZ(args.len, &buf2); + }, + .type = typeInfo.params[argInfo.fields.len].type.?, + .is_comptime = false, + .alignment = @alignOf(typeInfo.params[0].type.?), + .default_value_ptr = null, + }; + const newStruct = std.builtin.Type{ + .@"struct" = .{ + .backing_integer = argInfo.backing_integer, + .decls = argInfo.decls, + .fields = fields[0..args.len+1], + .is_tuple = argInfo.is_tuple, + .layout = argInfo.layout, + } + }; + + // std.debug.print("{any}", .{fields[0..3]}); + const t = @Type(newStruct); + var newArgs: t = undefined; + for (@typeInfo(t).@"struct".fields, 0..) |f, i| { + if (i == args.len) { + @field(newArgs, f.name) = arg; + } else @field(newArgs, f.name) = args[i]; + + } + return curryHelper(func, newArgs); + } + }; +} + +fn intToStringZ(int: u32, buf: []u8) ![:0]u8 { + return try std.fmt.bufPrintZ(buf, "{}", .{int}); +} + +// TODO: Add diff --git a/src/util.zig b/src/util.zig new file mode 100644 index 0000000..8de989e --- /dev/null +++ b/src/util.zig @@ -0,0 +1,16 @@ +const std = @import("std"); +const Type = std.builtin.Type; + +pub fn typeVerify(T: type, expected: anytype) Type { + const expectedType = @TypeOf(expected); + const expectedTypeInfo = @typeInfo(expectedType); + if (expectedTypeInfo != .@"struct") + @compileError("Expected struct or tuple, found " ++ @typeName(expectedType)); + const realTypeInfo = @typeInfo(T); + for (expected) |e| { + if(realTypeInfo == e) return realTypeInfo; + } + for (expected) |e| + @compileError("Expected one of " ++ @tagName(e) ++ ", found " ++ @typeName(T)); + return realTypeInfo; +} |
