- Функции как аргументы
- Анонимные функции в аргументах
- Функции в стандартной библиотеке
- Функции как результат
- Функции-обёртки (декораторы)
- Именованные типы для функций
- Ошибки и функции высшего порядка
- Параллель с map, filter и reduce
- Итоги
Когда мы только начинаем писать код, функции кажутся чем-то простым: есть входные данные, есть результат. Но чем больше задач мы решаем, тем чаще повторяется одна и та же схема — меняется только маленькая деталь. Чтобы не копировать код, в Go можно использовать функции высшего порядка.
Функция высшего порядка — это функция, которая принимает другую функцию как аргумент или возвращает функцию как результат. В Go это возможно, потому что функции — это значения, такие же, как строки или числа. Их можно сохранять в переменные, передавать и возвращать.
Функции как аргументы
Допустим, у нас есть срез чисел, и мы хотим применить преобразование к каждому элементу. Вместо того чтобы писать много похожих функций, можно вынести общий алгоритм.
package main
import "fmt"
// applyToEach применяет функцию f к каждому элементу среза
func applyToEach(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = f(v) // применяем функцию f
}
return result
}
func double(x int) int {
return x * 2
}
func square(x int) int {
return x * x
}
func main() {
numbers := []int{1, 2, 3, 4, 5}
doubled := applyToEach(numbers, double)
squared := applyToEach(numbers, square)
fmt.Println("Исходные:", numbers)
fmt.Println("Удвоенные:", doubled)
fmt.Println("Возведённые в квадрат:", squared)
}
Функция applyToEach
— это функция высшего порядка. Она не знает, что именно нужно сделать с числом, и делегирует это другой функции.
Анонимные функции в аргументах
Иногда создавать отдельную функцию ради одной строчки — слишком громоздко. Тогда можно передать анонимную функцию прямо в вызове.
func main() {
numbers := []int{1, 2, 3, 4, 5}
plusOne := applyToEach(numbers, func(x int) int {
return x + 1
})
fmt.Println("Прибавили 1:", plusOne)
}
Функции в стандартной библиотеке
В Go часто встречается приём передачи функций в стандартных пакетах. Например, в sort.Slice
мы задаём правила сортировки через функцию сравнения:
import (
"fmt"
"sort"
)
func main() {
people := []string{"Иван", "Мария", "Алексей", "Сергей"}
// сортировка по длине имени
sort.Slice(people, func(i, j int) bool {
return len(people[i]) < len(people[j])
})
fmt.Println(people) // [Иван Сергей Мария Алексей]
}
Здесь видно, что функции высшего порядка — это не теория, а повседневный инструмент.
Функции как результат
Функция может возвращать не только значения, но и другие функции. Такой приём называется замыканием.
// makeMultiplier возвращает функцию-множитель
func makeMultiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := makeMultiplier(2)
triple := makeMultiplier(3)
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}
makeMultiplier
создаёт функцию, которая “помнит” коэффициент factor
.
Функции-обёртки (декораторы)
Функции высшего порядка можно использовать для оборачивания логики — например, добавить логирование:
import "fmt"
// decorator принимает функцию и возвращает новую с логированием
func decorator(f func(int) int) func(int) int {
return func(x int) int {
fmt.Println("Вызов с:", x)
result := f(x)
fmt.Println("Результат:", result)
return result
}
}
func square(x int) int {
return x * x
}
func main() {
loggedSquare := decorator(square)
loggedSquare(5)
}
Именованные типы для функций
Чтобы код выглядел аккуратнее, можно объявлять собственный тип для функций:
type Transformer func(int) int
func apply(nums []int, f Transformer) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = f(v)
}
return result
}
Это особенно удобно, если функция принимает сразу несколько функций как аргументы.
Ошибки и функции высшего порядка
Иногда функции возвращают ошибки. В таком случае обёртка тоже должна их обрабатывать:
type Worker func(string) (string, error)
func safeRun(f Worker) Worker {
return func(s string) (string, error) {
result, err := f(s)
if err != nil {
return "", fmt.Errorf("ошибка: %w", err)
}
return result, nil
}
}
Параллель с map, filter и reduce
В Go нет встроенных функций map
, filter
и reduce
, как в функциональных языках, но мы можем написать их сами:
func Map(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, v := range nums {
result[i] = f(v)
}
return result
}
func Filter(nums []int, f func(int) bool) []int {
result := []int{}
for _, v := range nums {
if f(v) {
result = append(result, v)
}
}
return result
}
func Reduce(nums []int, f func(int, int) int, init int) int {
result := init
for _, v := range nums {
result = f(result, v)
}
return result
}
Итоги
Функции высшего порядка в Go позволяют писать код чище и понятнее. Они помогают вынести общий алгоритм в одну функцию и подставлять разные детали в виде других функций. При этом не обязательно создавать отдельные именованные функции — нужную логику можно написать прямо на месте с помощью анонимных. Такой приём даёт возможность реализовать преобразование коллекций наподобие map
, filter
и reduce
.
Функции можно использовать и для обёрток: например, добавить к существующей операции логирование или обработку ошибок. Чтобы код оставался читаемым, удобно объявлять собственные типы для функций и работать с ними так же, как с любыми другими данными.
В Go функция — это значение. Её можно хранить в переменной, передавать в другую функцию и возвращать как результат. Это делает язык гибким и позволяет строить простые, но мощные конструкции.
Самостоятельная работа
Используя функцию-декоратор расширим поведение других функций без их изменения. Например мы можем добавить логирование или профилирование.
Реализуйте функцию TimeDecorator
, которая принимает функцию func(int) int
и возвращает новую функцию с такой же сигнатурой. Новая функция измеряет время выполнения исходной функции, выводит его в консоль, а затем возвращает результат.
Пример использования:
func SlowDouble(x int) int {
sum := 0
for i := 0; i < 1_000_000; i++ {
sum += x
}
return sum
}
func main() {
timedDouble := timeDecorator(SlowDouble)
fmt.Println(timedDouble(5)) // В консоль: "Execution time: ...", затем печать результата
}
Подсказки:
- Используйте пакет
time
. - Внутри возвращаемой функции зафиксируйте
start := time.Now()
, затем вызовите исходную функцию, потом измерьтеtime.Since(start)
Показать решение
package main
import (
"fmt"
"time"
)
func TimeDecorator(f func(int) int) func(int) int {
return func(x int) int {
start := time.Now()
result := f(x)
duration := time.Since(start)
fmt.Println("Execution time:", duration)
return result
}
}
func main() {
slowDouble := func(x int) int {
sum := 0
for i := 0; i < 1_000_000; i++ {
sum += x
}
return sum
}
timedDouble := TimeDecorator(slowDouble)
fmt.Println(timedDouble(5))
}
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.