A Generic Toolkit
Once you can parameterize functions over element types, a handful of
higher-order helpers cover a surprising amount of slice code. You met Map in
the intro chapter; its two siblings, Filter and Reduce, complete the classic
trio. Together they let you describe what you want from a slice instead of
writing the same index-and-append loop over and over.
Filter: keep the ones that pass
Filter walks a slice and keeps the elements a predicate approves of,
returning a new slice:
func Filter[T any](xs []T, keep func(T) bool) []T {
out := make([]T, 0, len(xs))
for _, x := range xs {
if keep(x) {
out = append(out, x)
}
}
return out
}
One type parameter is enough — the output holds the same type as the input.
any is the right constraint because Filter never looks inside an element;
it only hands each one to keep and copies the winners. The input slice is left
untouched.
Reduce: fold a slice to a single value
Reduce (also called fold) collapses a slice into one accumulated result. It
takes a starting value and a combining function, and threads an accumulator
through every element left to right:
func Reduce[T, U any](xs []T, init U, f func(U, T) U) U {
acc := init
for _, x := range xs {
acc = f(acc, x)
}
return acc
}
Two type parameters, because the accumulator type U can differ from the
element type T. Sum a []int into an int, join a []string into one
string, tally a []Order into a float64 total — same function each time.
On an empty slice the loop never runs, so Reduce returns init unchanged,
which is exactly the right identity element (0 for a sum, "" for
concatenation).
Inference keeps call sites clean
You almost never spell out the type arguments. The compiler infers them from the values you pass:
nums := []int{1, 2, 3, 4, 5, 6}
isEven := func(n int) bool { return n%2 == 0 }
add := func(acc, n int) int { return acc + n }
evens := Filter(nums, isEven) // T = int, inferred
total := Reduce(evens, 0, add) // T = int, U = int, inferred
Because they share the shape "slice in, slice or value out", these helpers
compose cleanly — the result of Filter drops straight into Reduce:
Reduce(Filter(nums, isEven), 0, add) // sum of the even numbers → 12
No Reduce[int, int](...) noise at the call site, and no type assertions
anywhere — the element types flow through the whole pipeline, fully checked.
Why Go does it this way
A pre-generics toolkit like this had to choose between writing SumInts,
SumFloats, FilterStrings… by hand, or going through interface{} and
paying with runtime assertions. Generic Filter and Reduce give you one
implementation, reused at any element type, with the compiler verifying every
call — and inference means the ergonomics barely differ from an ordinary
function call. Go deliberately keeps this to a few sharp tools rather than a
sprawling standard library of combinators: reach for them where they clarify,
and a plain for loop is still perfectly idiomatic when it reads better. In the
exercise you'll implement both Filter and Reduce, then use them together.
Practice
1. Filter and Reduce
AdvancedImplement two generic slice helpers:
FilterandReduce.func Filter[T any](xs []T, keep func(T) bool) []T func Reduce[T, U any](xs []T, init U, f func(U, T) U) UFilterreturns a new slice containing only the elements for whichkeep(x)is true, in their original order. It must not modifyxs.Reducefoldsxsleft to right. Start the accumulator atinit, then for each elementxsetacc = f(acc, x), and return the final accumulator. On an empty slice it returnsinitunchanged.The tests cover:
Filterthe even ints out of a slice, andFilterthe non-empty strings.Reducea sum of ints (init0) and a concatenation of strings (init"").Reduceon an empty slice returnsinit.- The two composed together:
Reduce(Filter(nums, isEven), 0, add).
You won't need to write any type arguments by hand — inference fills in
TandUfrom the values you pass.package main import "fmt" // Filter returns a new slice holding the elements of xs for which keep // returns true, in their original order. func Filter[T any](xs []T, keep func(T) bool) []T { // TODO: implement return nil } // Reduce folds xs left to right: starting from init, it applies f(acc, x) to // each element and returns the final accumulator. func Reduce[T, U any](xs []T, init U, f func(U, T) U) U { // TODO: implement return init } func main() { nums := []int{1, 2, 3, 4, 5, 6} evens := Filter(nums, func(n int) bool { return n%2 == 0 }) fmt.Println("evens:", evens) total := Reduce(nums, 0, func(acc, n int) int { return acc + n }) fmt.Println("total:", total) }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.