Можно ли передать функцию как параметр? Да, и вот почему это меняет всё
Вы когда-нибудь задумывались, можно ли в Go передать одну функцию внутрь другой, как обычную переменную? Если вы пришли из мира Python или JavaScript, для вас это привычный паттерн. Но в Go, несмотря на всю его строгость, функции как параметры — это не просто "можно", это один из самых мощных инструментов языка.
Я помню своё первое знакомство с этой концепцией: я писал утилиту для обработки логов и понял, что копипащу один и тот же цикл с разной логикой внутри. Решение пришло, когда я осознал, что могу просто передать нужную функцию как аргумент. Код стал чище, короче и понятнее. В этой статье я покажу вам, как освоить передачу функции в функцию Go — от простых примеров до реальных паттернов, которые вы сможете использовать завтра на работе.
Кстати, мы на PythonLib часто видим вопросы вроде "Can functions be passed as parameters?" на Stack Overflow с тысячами просмотров. Давайте раз и навсегда разберём эту тему на примере Go.
Что такое функции как параметры в Go?
В Go функции — это объекты первого класса. Это значит, что их можно:
- Присваивать переменным
- Передавать в другие функции
- Возвращать из функций
- Хранить в структурах и слайсах
Когда мы говорим о функции как параметре, мы имеем в виду, что одна функция принимает другую функцию в качестве аргумента и вызывает её внутри себя. Звучит сложно? На самом деле это проще, чем кажется.
Синтаксис: как объявить функцию, принимающую другую функцию
В Go нужно явно указывать тип функции-параметра. Выглядит это так:
// Объявляем тип функции
type Transformer func(string) string
// Функция, которая принимает другую функцию
func processText(text string, transform Transformer) string {
return transform(text)
}
Или можно объявить тип прямо в сигнатуре:
func processText(text string, transform func(string) string) string {
return transform(text)
}
Оба варианта рабочие. Первый удобнее, если вы планируете переиспользовать тип. Второй — для быстрых решений.
Простой пример: передаём функцию приветствия
Давайте напишем что-то жизненное. Представьте, что у вас есть функция, которая форматирует имя пользователя, и вы хотите подставлять разные приветствия:
package main
import "fmt"
// Тип для функции-приветствия
type Greeter func(name string) string
// Функция, принимающая приветствие
func greetUser(name string, greeter Greeter) {
message := greeter(name)
fmt.Println(message)
}
func main() {
// Обычное приветствие
normalGreet := func(name string) string {
return "Привет, " + name + "!"
}
// Формальное приветствие
formalGreet := func(name string) string {
return "Уважаемый(ая) " + name + ", добрый день."
}
greetUser("Анна", normalGreet)
greetUser("Анна", formalGreet)
}
Вывод:
Привет, Анна!
Уважаемый(ая) Анна, добрый день.
Видите? Мы просто передали разные функции в одну и ту же greetUser. Это и есть передача функции как аргумента в действии.
Callback-функции: когда нужно обработать результат
Один из самых частых сценариев использования функций как параметров — это callback'и. Вы выполняете какую-то операцию и вызываете переданную функцию, когда операция завершена.
package main
import (
"fmt"
"time"
)
// Функция, которая имитирует долгую загрузку данных
func fetchData(callback func(string)) {
fmt.Println("Загрузка данных...")
time.Sleep(2 * time.Second) // Имитация задержки
data := "Важные данные из базы"
callback(data) // Вызываем callback с результатом
}
func main() {
// Передаём функцию, которая будет вызвана после загрузки
fetchData(func(result string) {
fmt.Println("Получено:", result)
})
fmt.Println("Программа продолжает работу...")
time.Sleep(3 * time.Second)
}
Этот паттерн критически важен для асинхронного программирования. В Go, конечно, есть горутины и каналы, но callback'и никуда не делись — они просто работают в паре с ними.
Фильтрация данных: практический пример
Давайте реализуем утилиту для фильтрации слайса. Это классика функционального программирования в Go:
package main
import "fmt"
// Функция-фильтр принимает слайс и функцию-предикат
func filter(numbers []int, predicate func(int) bool) []int {
var result []int
for _, n := range numbers {
if predicate(n) {
result = append(result, n)
}
}
return result
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// Фильтруем чётные числа
evens := filter(nums, func(n int) bool {
return n%2 == 0
})
fmt.Println("Чётные:", evens)
// Фильтруем числа больше 5
greaterThanFive := filter(nums, func(n int) bool {
return n > 5
})
fmt.Println("Больше 5:", greaterThanFive)
}
Вывод:
Чётные: [2 4 6 8 10]
Больше 5: [6 7 8 9 10]
Обратите внимание: мы написали одну функцию filter, а логику фильтрации передаём извне. Это и есть суть передачи функции в функцию Go — отделение алгоритма от конкретной реализации.
Продвинутый паттерн: middleware в HTTP-сервере
Теперь, когда вы освоили базу, давайте посмотрим на реальный продакшн-код. В Go middleware для HTTP-сервера — это классический пример функций как параметров.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// Тип для middleware
type Middleware func(http.HandlerFunc) http.HandlerFunc
// Логирующий middleware
func Logger(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("Запрос: %s %s", r.Method, r.URL.Path)
next(w, r) // Вызываем оригинальный обработчик
log.Printf("Выполнено за: %v", time.Since(start))
}
}
// Middleware для проверки авторизации
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// Тут могла бы быть проверка токена
next(w, r)
}
}
// Применяем middleware к обработчику
func applyMiddleware(handler http.HandlerFunc, middlewares ...Middleware