Generic Map and Filter
Before Go 1.18 there were two ways to write a function that doubles every
element of a slice: write it once per element type, or take []any and pay for
a type assertion on every element. Neither is good. Type parameters are the
third way.
func Map[T, U any](s []T, f func(T) U) []U
[T, U any] is the type parameter list. T and U are types the caller
supplies, any is the constraint on each - here, no constraint at all. Inside
the body T and U behave like ordinary types you happen not to know the name
of.
Inference
You almost never write the types at the call site:
lengths := Map(words, func(s string) int { return len(s) })
The compiler reads words as []string, so T is string, and the function
literal returns int, so U is int. Explicit instantiation - Map[string, int](...) - is legal and occasionally necessary, but if you find yourself
writing it everywhere, the signature is usually the problem.
The zero value of a type parameter
You cannot write return nil or return 0 for a T, because T might be
neither. There is one spelling that always works:
var zero T
return zero, false
That is the idiom for "no result" in a generic function, and it is why the comma-ok pattern shows up so often here.
What you can do with a T
Only what the constraint allows. Under any you can assign it, pass it, put it
in a slice, and compare it to nothing at all - not even ==, because not every
Go type is comparable. Adding comparable as the constraint buys you == and
!=, which is exactly what a Contains or a map key needs. The next challenge
is about constraints that buy you arithmetic.
Allocating the result
make([]U, 0, len(s)) for a filter, make([]U, len(s)) for a map. A nil slice
appends fine, so the length is a performance choice, not a correctness one - but
these functions return their result to a caller who may compare it, so be
deliberate: this file's functions all return an empty non-nil slice for an
empty input.
Task
Implement Map, Filter, Reduce, Keys, Contains, IndexOf and First.
The signatures are given; the type parameter lists on some of them are not.
Hints
MapandFilterdiffer in one line: one appendsf(v), the other appendsvwhenkeep(v)is true.Reducethreads an accumulator: start atinit, andacc = f(acc, v)for each element. Its two type parameters are the element type and the accumulator type, and they are not the same thing.KeysneedsK comparable, because a map key is comparable by definition. The order of a map range is random, so the test sorts what you return.ContainsandIndexOfcompare elements, so theirTiscomparable, notany.IndexOfreturns -1 for a miss.Firstreturns(T, bool). Usevar zero Tfor the miss.