Write generic Map, Filter and Reduce for slices. Why aren't they in the standard library's slices package?
Question 136MediumGo 1.22 to 1.25
func Map[T, U any](xs []T, f func(T) U) []U {
out := make([]U, len(xs))
for i, x := range xs {
out[i] = f(x)
}
return out
}
func Filter[S ~[]E, E any](xs S, keep func(E) bool) S {
var out S
for _, x := range xs {
if keep(x) {
out = append(out, x)
}
}
return out
}
func Reduce[T, A any](xs []T, init A, f func(A, T) A) A {
acc := init
for _, x := range xs {
acc = f(acc, x)
}
return acc
}
nums := []int{1, 2, 3, 4}
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
strs := Map(evens, strconv.Itoa)
total := Reduce(nums, 0, func(a, n int) int { return a + n })
fmt.Println(strs, total) // [2 4] 10
Why the Go team left them out:
- A plain
forloop is usually clearer. - Each step allocates an intermediate slice, whereas a loop fuses the work.
- Without generic methods you cannot chain fluently, so nested calls read inside-out.
- Go 1.23 iterators (
iter.Seq) are the preferred building block for lazy, fused pipelines. The standard library hasslices.Collect,slices.Valuesandmaps.Keys, but deliberately has noMaporFilter.
Note that Filter uses S ~[]E to keep the caller's named type. An in-place variant can reuse xs[:0], which is what slices.DeleteFunc does.
More on Generics
- Q134Implement a generic
Set[T]with union, intersection and iteration. - Q135Design a thread-safe generic cache with a
GetOrLoadmethod. What concurrency pitfalls exist? - Q137What is
golang.org/x/exp/constraints, how does it relate tocmp.Ordered, and how do you define your own numeric constraint? - Q138Explain the
cmppackage:Compare,Less,Or. How do you do a multi-key sort, and how is NaN handled? - Q139Which generic helpers in
slicesandmapsshould a senior Go developer know, and what are their gotchas? - Q140How do generics combine with Go 1.23 iterators (
iter.Seq)? Write lazyFilterandMapadapters and explain theyieldcontract.