A Zig library providing higher-order functions for working with comptime-known arrays. These functions leverage Zig's powerful comptime capabilities to provide zero-cost abstractions for functional programming patterns.
map- Transform each element of an arrayfilter- Filter elements based on a predicatefoldLeft- Left-to-right fold/reduce operationfoldRight- Right-to-left fold/reduce operation
All functions work with comptime-known arrays and have zero runtime overhead when arrays are comptime.
Add this to your build.zig.zon:
.dependencies = .{
.hof = .{
.path = "path/to/zig-hof",
},
}Then in your build.zig:
const hof = b.dependency("hof", .{}).module("hof");
exe.addModule("hof", hof);Import the module:
const hof = @import("hof");Maps each element of a comptime-known array to a new value using a function. Returns an array of the same length.
Signature:
pub fn map(comptime arr: anytype, comptime f: anytype) [Len(arr)]MapOut(arr, f)Example:
const arr = [4]i32{ 1, 2, 3, 4 };
const Double = struct {
fn double(x: i32) i32 {
return x * 2;
}
};
const result = hof.map(arr, Double.double);
// result: [4]i32{ 2, 4, 6, 8 }Type conversion example:
const arr = [3]i32{ 1, 2, 3 };
const ToFloat = struct {
fn toFloat(x: i32) f64 {
return @as(f64, @floatFromInt(x));
}
};
const result = hof.map(arr, ToFloat.toFloat);
// result: [3]f64{ 1.0, 2.0, 3.0 }Filters elements of a comptime-known array based on a predicate. Returns an array containing only the matching elements (size determined at comptime).
Signature:
pub fn filter(comptime arr: anytype, comptime pred: anytype) [countIf(arr, pred)]Elem(arr)Example:
const arr = [5]i32{ 1, 2, 3, 4, 5 };
const IsEven = struct {
fn isEven(x: i32) bool {
return @rem(x, 2) == 0;
}
};
const result = hof.filter(arr, IsEven.isEven);
// result: [2]i32{ 2, 4 }Folds an array from left to right using a binary function and an initial value. Also known as reduce or foldl in other languages.
Signature:
pub fn foldLeft(comptime arr: anytype, initial: anytype, comptime folder: anytype) @TypeOf(initial)Example:
const arr = [4]i32{ 1, 2, 3, 4 };
const Add = struct {
fn add(acc: i32, x: i32) i32 {
return acc + x;
}
};
const sum = hof.foldLeft(arr, 0, Add.add);
// sum: 10 (0 + 1 + 2 + 3 + 4)Product example:
const arr = [4]i32{ 2, 3, 4, 5 };
const Multiply = struct {
fn multiply(acc: i32, x: i32) i32 {
return acc * x;
}
};
const product = hof.foldLeft(arr, 1, Multiply.multiply);
// product: 120 (1 * 2 * 3 * 4 * 5)Folds an array from right to left using a binary function and an initial value. Also known as foldr in other languages.
Signature:
pub fn foldRight(comptime arr: anytype, initial: anytype, comptime folder: anytype) @TypeOf(initial)Note: The function signature is fn(x: T, acc: Acc) Acc (element first, then accumulator), which is the opposite of foldLeft.
Example:
const arr = [4]i32{ 1, 2, 3, 4 };
const Add = struct {
fn add(x: i32, acc: i32) i32 {
return x + acc;
}
};
const sum = hof.foldRight(arr, 0, Add.add);
// sum: 10 (same result as foldLeft for addition)Associativity difference:
For non-associative operations, foldLeft and foldRight produce different results:
const arr = [3]i32{ 10, 3, 2 };
const SubtractLeft = struct {
fn subtract(acc: i32, x: i32) i32 {
return acc - x;
}
};
const SubtractRight = struct {
fn subtract(x: i32, acc: i32) i32 {
return x - acc;
}
};
const left = hof.foldLeft(arr, 0, SubtractLeft.subtract);
// left: -15 (0 - 10 - 3 - 2)
const right = hof.foldRight(arr, 0, SubtractRight.subtract);
// right: 9 (10 - (3 - (2 - 0)))These functions compose nicely together:
const arr = [5]i32{ 1, 2, 3, 4, 5 };
const Double = struct {
fn double(x: i32) i32 { return x * 2; }
};
const IsEven = struct {
fn isEven(x: i32) bool { return @rem(x, 2) == 0; }
};
const Add = struct {
fn add(acc: i32, x: i32) i32 { return acc + x; }
};
// Double all elements, filter evens, then sum
const doubled = hof.map(arr, Double.double);
const evens = hof.filter(doubled, IsEven.isEven);
const sum = hof.foldLeft(evens, 0, Add.add);
// sum: 30 (2 + 4 + 6 + 8 + 10)- Zig 0.15.2 or later