Посмотрим на практические функции, которые упрощают повседневную работу со слайсами и мапами. А также на то, что Go уже включает из коробки в пакетах
slices и maps.Map, Filter, Reduce
Go не предоставляет эти функции в стандартной библиотеке, но с дженериками их легко написать самостоятельно.
Map трансформирует каждый элемент слайса:func Map[T, U any](slice []T, fn func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice {
result[i] = fn(v)
}
return result
}Filter оставляет только элементы, подходящие под условие:func Filter[T any](slice []T, predicate func(T) bool) []T {
var result []T
for _, v := range slice {
if predicate(v) {
result = append(result, v)
}
}
return result
}Reduce сворачивает слайс в одно значение:func Reduce[T, U any](slice []T, initial U, fn func(U, T) U) U {
result := initial
for _, v := range slice {
result = fn(result, v)
}
return result
}Пример использования всех трёх вместе:
numbers := []int{1, 2, 3, 4, 5}
doubled := Map(numbers, func(n int) int { return n * 2 })
// [2, 4, 6, 8, 10]
evens := Filter(numbers, func(n int) bool { return n%2 == 0 })
// [2, 4]
sum := Reduce(numbers, 0, func(acc, n int) int { return acc + n })
// 15Keys и Values для map
Ещё одна частая задача, получить ключи или значения мапы отдельным слайсом:
func Keys[K comparable, V any](m map[K]V) []K {
keys := make([]K, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}Аналогично для значений. Порядок не гарантирован, как и при обычной итерации по map в Go.
Дженерик-кеш
Потокобезопасный кеш на дженериках. Полезная штука для сервисов, где нужно кешировать объекты разных типов:
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]) Get(key K) (V, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
val, ok := c.items[key]
return val, ok
}
func (c *Cache[K, V]) Set(key K, value V) {
c.mu.Lock()
defer c.mu.Unlock()
c.items[key] = value
}Вместо
map[string]interface{} с приведением типов получаем типобезопасный контейнер. Компилятор ловит ошибки за вас.Пакеты slices и maps
Начиная с Go 1.21, стандартная библиотека включает пакеты
slices и maps с готовыми дженерик-функциями. Прежде чем писать свои утилиты, стоит заглянуть сюда:
import (
"slices"
"maps"
)
numbers := []int{3, 1, 4, 1, 5, 9}
slices.Sort(numbers)
slices.Contains(numbers, 4) // true
slices.Index(numbers, 4) // позиция элемента
slices.Max(numbers) // 9
slices.Min(numbers) // 1
slices.Reverse(numbers)
Для сортировки с кастомной функцией сравнения есть
slices.SortFunc:slices.SortFunc(numbers, func(a, b int) int {
return b - a // по убыванию
})Пакет
maps предлагает Clone, Equal и DeleteFunc:m := map[string]int{"a": 1, "b": 2}
copy := maps.Clone(m)
maps.DeleteFunc(m, func(k string, v int) bool {
return v < 2
})Для получения ключей и значений в виде слайсов используется связка
maps.Keys / maps.Values с slices.Collect:keys := slices.Collect(maps.Keys(m))
vals := slices.Collect(maps.Values(m))
Практические советы
Используйте дженерики, когда у вас действительно есть дублирование кода для разных типов. Если функция работает только с одним типом, дженерики не нужны.
Выбирайте максимально точный constraint.
comparable вместо any, если нужно сравнение. cmp.Ordered вместо comparable, если нужны операторы < и >.Дженерики в Go не пытаются заменить всю систему типов. Это точечный инструмент для устранения дублирования.
Вместе с пакетами
slices и maps они закрывают большинство задач, которые раньше решались через interface{}, рефлексию или генерацию кода.📍 Навигация: Вакансии • Задачи • Собесы
🐸 Библиотека Go-разработчика
#GoDeep