Go: Дженерики

Теория: Типовые функции

В работе с коллекциями часто повторяется одна и та же логика: преобразовать все элементы, оставить только подходящие или свести список к одному значению. В других языках программирования для этого существуют стандартные функции map, filter и reduce. В Go их можно реализовать самостоятельно, а благодаря дженерикам — так, чтобы они работали с любыми типами.

Функция отображения Map получает срез элементов и функцию-преобразователь. Она создаёт новый срез, куда кладёт результат работы преобразователя для каждого элемента. Параметры типа T и R задают тип входных и выходных значений.

func Map[T, R any](in []T, fn func(T) R) []R {
	out := make([]R, len(in))
	for i, v := range in {
		out[i] = fn(v)
	}
	return out
}

Если вызвать её так:

nums := []int{1, 2, 3}
squared := Map(nums, func(x int) int { return x * x })
// [1 4 9]

То каждый элемент будет возведён в квадрат. Та же функция работает и для строк, если преобразователь возвращает, например, длину:

words := []string{"go", "generics", "map"}
lengths := Map(words, func(s string) int { return len(s) })
// [2 8 3]

Функция фильтрации Filter оставляет только те элементы, которые удовлетворяют условию. В сигнатуре параметр типа один, так как на входе и выходе остаются те же элементы.

func Filter[T any](in []T, pred func(T) bool) []T {
	out := make([]T, 0, len(in))
	for _, v := range in {
		if pred(v) {
			out = append(out, v)
		}
	}
	return out
}

Пример использования:

nums := []int{1, 2, 3, 4, 5, 6}
evens := Filter(nums, func(x int) bool { return x%2 == 0 })
// [2 4 6]

Та же логика работает и для строк:

words := []string{"go", "java", "python", "c"}
long := Filter(words, func(s string) bool { return len(s) > 2 })
// ["java" "python"]

Функция свёртки Reduce объединяет элементы среза в одно значение. Она принимает аккумулятор начального значения и функцию объединения. Тип аккумулятора R может совпадать с типом элементов или отличаться от него.

func Reduce[T, R any](in []T, init R, combine func(R, T) R) R {
	acc := init
	for _, v := range in {
		acc = combine(acc, v)
	}
	return acc
}

Примеры использования:

nums := []int{1, 2, 3, 4}
sum := Reduce(nums, 0, func(acc, x int) int { return acc + x })
// 10

words := []string{"Go", "is", "fun"}
sentence := Reduce(words, "", func(acc, s string) string {
    if acc == "" {
        return s
    }
    return acc + " " + s
})
// "Go is fun"

Такие обобщённые функции для коллекций дают сразу несколько преимуществ. Они устраняют дублирование кода, делают его выразительным и лаконичным. Строгая типизация сохраняется: компилятор знает, какие типы подставлены, и не позволит вызвать функцию с неподходящими аргументами. Производительность остаётся на уровне обычных циклов, потому что внутри нет приведения к interface{}.

Особую роль здесь играют замыкания. В примерах выше условия и преобразования описываются как функции, которые можно создавать прямо в месте вызова. Эти функции замыкают переменные внешнего окружения. Например:

threshold := 10
greater := Filter(nums, func(x int) bool { return x > threshold })
// оставляет числа больше 10

Переменная threshold не передаётся в сигнатуре, но доступна внутри функции-предиката. Это делает код компактным и удобным: параметры типа описывают только структуру данных, а детали логики остаются на стороне замыканий.

В результате обобщённые реализации Map, Filter и Reduce становятся строительными блоками для работы с любыми коллекциями. Их можно комбинировать: сначала отфильтровать данные, затем преобразовать и в конце свести к одному результату. Всё это выполняется без дублирования, с полным контролем типов и в привычном для Go стиле.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845