Зарегистрируйтесь, чтобы продолжить обучение

Типовые функции 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 стиле.


Самостоятельная работа

Реализуем обобщённые функции высшего порядка.

  • Реализуйте Any[T any](in []T, pred func(T) bool) bool — возвращает true, если хотя бы один элемент удовлетворяет предикату.
  • Реализуйте Count[T any](in []T, pred func(T) bool) int — возвращает количество элементов, удовлетворяющих предикату.

Проверьте на срезе чисел и строк (например, «есть ли чётные», «сколько строк длиннее 3»).

nums := []int{1,2,3,4,5,6}
fmt.Println(Any(nums, func(x int) bool { return x%2==0 })) // true
fmt.Println(Count(nums, func(x int) bool { return x%2==0 })) // 3

words := []string{"go","gen","map","filters"}
fmt.Println(Any(words, func(s string) bool { return len(s) > 3 })) // true
fmt.Println(Count(words, func(s string) bool { return len(s) > 3 })) // 2
Показать решение
package main

import "fmt"

func Any[T any](in []T, pred func(T) bool) bool {
    for _, v := range in {
        if pred(v) {
            return true
        }
    }
    return false
}

func Count[T any](in []T, pred func(T) bool) int {
    c := 0
    for _, v := range in {
        if pred(v) {
            c++
        }
    }
    return c
}

func main() {
    nums := []int{1, 2, 3, 4, 5, 6}
    fmt.Println(Any(nums, func(x int) bool { return x%2 == 0 }))   // true
    fmt.Println(Count(nums, func(x int) bool { return x%2 == 0 })) // 3

    words := []string{"go", "gen", "map", "filters"}
    fmt.Println(Any(words, func(s string) bool { return len(s) > 3 }))   // true
    fmt.Println(Count(words, func(s string) bool { return len(s) > 3 })) // 2
}

Дополнительные материалы

  1. Wikipedia — Map (higher-order function)
  2. Wikipedia — Filter (higher-order function)
  3. Wikipedia — Fold/Reduce (higher-order function)

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff