1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
|