Lesson 11 of 12
Generics
Learn how to write and types that work with many types using type and constraints, available since Go 1.18.
package main
import (
"fmt"
"cmp"
)
// [T any] declares a type parameter. any is an alias for interface{}.
func Map[T, U any](items []T, fn func(T) U) []U {
out := make([]U, 0, len(items))
for _, item := range items {
out = append(out, fn(item))
}
return out
}
func Filter[T any](items []T, keep func(T) bool) []T {
var out []T
for _, item := range items {
if keep(item) {
out = append(out, item)
}
}
return out
}
func Reduce[T, A any](items []T, initial A, fn func(A, T) A) A {
acc := initial
for _, item := range items {
acc = fn(acc, item)
}
return acc
}
func main() {
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
labels := Map(nums, func(n int) string { return fmt.Sprintf("#%d", n) })
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
sum := Reduce(nums, 0, func(a, n int) int { return a + n })
fmt.Println(doubled, labels, evens, sum)
}package main
import (
"cmp"
"fmt"
"golang.org/x/exp/constraints" // or declare your own constraint
)
// A constraint is an interface listing the permitted types.
type Number interface {
~int | ~int64 | ~float32 | ~float64 // ~ also allows named types with that base
}
func Sum[T Number](values []T) T {
var total T // zero value of whatever T is
for _, v := range values {
total += v
}
return total
}
// cmp.Ordered covers every type supporting < and >
func Max[T cmp.Ordered](values []T) (T, bool) {
var zero T
if len(values) == 0 {
return zero, false
}
best := values[0]
for _, v := range values[1:] {
if v > best {
best = v
}
}
return best, true
}
func main() {
fmt.Println(Sum([]int{1, 2, 3})) // 6
fmt.Println(Sum([]float64{1.5, 2.5})) // 4
fmt.Println(Max([]string{"b", "a", "c"})) // c true
}package main
import (
"fmt"
"sync"
)
// Generic types: a type-safe, concurrency-safe cache
type Cache[K comparable, V any] struct {
mu sync.RWMutex
items map[K]V
}
func NewCache[K comparable, V any]() *Cache[K, V] {
return &Cache[K, V]{items: make(map[K]V)}
}
func (c *Cache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}
func (c *Cache[K, V]) Get(key K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
v, ok := c.items[key]
return v, ok
}
// A generic stack
type Stack[T any] struct{ items []T }
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
func (s *Stack[T]) Pop() (T, bool) {
var zero T
if len(s.items) == 0 {
return zero, false
}
v := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return v, true
}
func main() {
c := NewCache[string, int]()
c.Set("hits", 42)
fmt.Println(c.Get("hits")) // 42 true
var s Stack[string]
s.Push("a"); s.Push("b")
fmt.Println(s.Pop()) // b true
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.