Day 31 — Generics intro
Day 31 — Generics intro
Stage IV · ~3h (theory-heavy)
Goal: Use type parameters and constraints confidently—write generic Map/Filter/Reduce helpers without abusing generics as a personality.
Generics (Go 1.18+) remove boilerplate, not design. Stage IV starts only after Stage III so you do not hide races behind type parameters.
Why this day exists
Before generics, common patterns needed:
interface{}/any+ type assertions
- Code generation
- Copy-paste with types renamed
Those still exist—but for pure algorithms over slices/maps/containers, type parameters are the right tool.
Theory 1 — Type parameters on functions
func Identity[T any](v T) T { return v }
x := Identity[int](3)
y := Identity("hi") // inference| Piece | Meaning |
|---|---|
[T any] |
Type parameter list; T constrained by any |
| Instantiation | Identity[int] or inferred from args |
any |
Alias for empty interface—unconstrained |
Multiple parameters
func Pair[A, B any](a A, b B) (A, B) { return a, b }Theory 2 — Constraints
A constraint is an interface limiting allowed type arguments.
func Min[T constraints.Ordered](a, b T) T {
if a < b {
return a
}
return b
}import "cmp" // Go 1.21+: cmp.Ordered
// or golang.org/x/exp/constraints historicallyGo 1.21+ prefers cmp.Ordered in many examples:
import "cmp"
func Min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}Inline interface constraints
type Number interface {
~int | ~int64 | ~float64
}
func Add[T Number](a, b T) T { return a + b }~ means underlying type: type MyInt int satisfies ~int.
Theory 3 — Approximation (~) and unions
type Stringy interface {
~string
}Union:
type Intish interface {
~int | ~int32 | ~int64
}Methods + types in one interface:
type Stringer interface {
~string
String() string // impossible? actually: type set + methods rules
}Know the rules at a high level: constraints define a type set. If unsure, keep constraints simple—any, cmp.Ordered, or small unions.
Theory 4 — Inference and explicit instantiation
Map([]int{1, 2}, func(x int) int { return x + 1 })
// compiler infers T, U
Map[int, string](xs, strconv.Itoa) // explicit when neededInference fails when arguments don’t pin types—then instantiate explicitly.
Theory 5 — Map / Filter / Reduce (stdlib-shaped)
Go did not put full FP combinators in stdlib early; slices and maps packages grew helpers. Still, writing your own teaches constraints.
func Map[T, U any](in []T, f func(T) U) []U {
out := make([]U, len(in))
for i, v := range in {
out[i] = f(v)
}
return out
}
func Filter[T any](in []T, keep func(T) bool) []T {
var out []T
for _, v := range in {
if keep(v) {
out = append(out, v)
}
}
return out
}
func Reduce[T, U any](in []T, init U, f func(U, T) U) U {
acc := init
for _, v := range in {
acc = f(acc, v)
}
return acc
}Prefer stdlib when available
import "slices"
slices.Sort(xs)
slices.Compact(xs)
// etc.Don’t reinvent a worse slices package for production—labs reinvent for learning.
Theory 6 — Generic interfaces (preview)
type Repo[T any] interface {
Get(id string) (T, error)
Save(T) error
}Useful; can also over-abstract. Day 32–33 drill judgment.
Theory 7 — What generics do not solve
- Performance magic (sometimes monomorphization helps; not why you start)
- Removing all interfaces
- Cross-package API churn without care
- Hiding concurrency mistakes
Worked example — keys of a map
func Keys[M ~map[K]V, K comparable, V any](m M) []K {
out := make([]K, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}comparable is required for map keys.
Worked example — pointer constraint pattern
func Zero[T any]() T {
var z T
return z
}Simple and useful for tests.
Lab 1 — Module + Map/Filter/Reduce
Workspace: ~/lab/90daysofx/01-go/day31
mkdir -p ~/lab/90daysofx/01-go/day31
cd ~/lab/90daysofx/01-go/day31
go mod init example.com/day31Implement Map, Filter, Reduce with tests:
go test -race ./...(Race optional here but keep the habit.)
Lab 2 — Min/Max with cmp.Ordered
func Min[T cmp.Ordered](a, b T) T
func Max[T cmp.Ordered](a, b T) T
func Clamp[T cmp.Ordered](v, lo, hi T) TTest with int, float64, string.
Lab 3 — ~ underlying types
type Celsius float64Write AddTemp[T ~float64](a, b T) T and show Celsius works.
Lab 4 — Replace interface{} code
Take a small pre-generic style helper (or write one) using any + assert; rewrite with type parameters. Compare error messages and call-site clarity.
Common gotchas
| Gotcha | Fix |
|---|---|
| Constraint too tight | Start with any, tighten |
| Constraint too loose | Compiler can’t use operators |
| Fighting inference | Explicit [T] |
| Generic everything | Day 33—push back |
Ignoring slices/maps |
Prefer stdlib in real code |
| Method receivers on generic funcs | Methods come Day 32 types |
Checkpoint
- Writes generic functions with inference
- Uses
cmp.Orderedor union constraints
- Explains
~
- Ships Map/Filter/Reduce tests
- Knows generics ≠ free performance
go testgreen
Commit
git add .
git commit -m "day31: generics intro — Map/Filter/Reduce + constraints"Write three personal gotchas before continuing.
Tomorrow
Day 32 — Generic structs & self-referential constraints: type-parameterized data structures and Go 1.26 self-referential type parameter notes.