Когда мы говорим «функция как аргумент», имеется в виду, что функция может быть передана внутрь другой функции так же, как мы обычно передаём число, строку или структуру. В Go это возможно потому, что функция — это значение, полноценный объект. У неё есть тип, и этот тип можно указать в параметрах.
Почему это важно? Представьте: у нас есть алгоритм, который одинаков для многих задач. Но внутри есть кусок логики, который зависит от ситуации. Если делать копию функции для каждого случая — будет куча дублирования. А если вынести различающуюся часть наружу и передавать её как функцию, код станет компактным и гибким.
Пример
package main
import "fmt"
// greet принимает строку (имя) и функцию, которая умеет печатать сообщение
func greet(name string, printer func(string)) {
message := "Привет, " + name
printer(message) // вызываем переданную функцию
}
func main() {
// Передаём готовую функцию fmt.Println
greet("Аня", fmt.Println)
// Вывод: Привет, Аня
// Передаём анонимную функцию, которая печатает в другом формате
greet("Ваня", func(s string) {
fmt.Println("***", s, "***")
})
// Вывод: *** Привет, Ваня ***
}
Здесь greet
сама не знает, как именно выводить сообщение. Она лишь формирует строку и передаёт её в функцию printer
, которую мы задаём как аргумент. Хотим печатать просто — передаём fmt.Println
. Хотим добавить звёздочки — пишем анонимную функцию.
Пример: калькулятор
package main
import "fmt"
// calculate принимает два числа и операцию над ними
func calculate(a, b int, op func(int, int) int) int {
return op(a, b)
}
func main() {
// Определим разные операции
add := func(x, y int) int { return x + y }
mul := func(x, y int) int { return x * y }
fmt.Println(calculate(3, 4, add)) // 7
fmt.Println(calculate(3, 4, mul)) // 12
// Можно сразу написать операцию в вызове
result := calculate(10, 5, func(x, y int) int {
return x - y
})
fmt.Println(result) // 5
}
Функция calculate
универсальна: она не знает, что такое «сложение» или «умножение». Она просто принимает два числа и вызывает переданную операцию. Это и есть сила функций как аргументов: мы описываем общий каркас, а детали подставляем на ходу.
Пример: фильтрация
Фильтрация — классика. Есть список значений, и нужно оставить только те, которые удовлетворяют условию.
package main
import "fmt"
// filter оставляет только элементы, которые проходят проверку
func filter(nums []int, check func(int) bool) []int {
var result []int
for _, n := range nums {
if check(n) {
result = append(result, n)
}
}
return result
}
func main() {
numbers := []int{1, 2, 3, 4, 5, 6}
// Оставляем только чётные
evens := filter(numbers, func(x int) bool { return x%2 == 0 })
fmt.Println(evens) // [2 4 6]
// Оставляем числа больше трёх
greater := filter(numbers, func(x int) bool { return x > 3 })
fmt.Println(greater) // [4 5 6]
}
Здесь filter
один для всех случаев. Вынесли переменную часть — «условие» — в функцию check
. Теперь её можно подставлять разную: хоть проверку на чётность, хоть проверку на диапазон, хоть на простое число.
Объяснение на пальцах
Можно думать так: когда мы пишем функцию с функцией-аргументом, мы словно оставляем пустое место — «дырку» — и говорим: «Заполню её позже нужной логикой».
Например:
- В
calculate
пустое место — это операция. - В
filter
пустое место — это условие. - В
greet
пустое место — это способ вывести сообщение. \
Ещё пример: обработчики событий
Это часто встречается в веб-серверах и GUI.
package main
import "fmt"
// onEvent принимает строку события и обработчик
func onEvent(event string, handler func(string)) {
fmt.Println("Событие:", event)
handler(event)
}
func main() {
onEvent("click", func(e string) {
fmt.Println("Обрабатываем:", e)
})
onEvent("hover", func(e string) {
fmt.Println("Навели мышку:", e)
})
}
Функция onEvent
не знает, что такое «клик» или «наведение». Ей всё равно. Она просто вызывает переданный обработчик. Это позволяет строить гибкие системы, где логика задаётся снаружи.
Функции как аргументы нужны, чтобы отделять общий алгоритм от частных деталей. Это убирает дублирование, делает код более выразительным и расширяемым. Алгоритм описывается один раз, а поведение можно менять, подставляя разные функции.
Самостоятельная работа
Потренируемся в создании функции, которая принимает на вход другие функции.
Напишите функцию transform
, которая принимает срез строк и функцию func(string) string
, преобразующую каждую строку. Верните новый срез с результатами применения этой функции.
Используйте её для обработки списка имён, передав анонимную функцию, которая:
- обрезает пробелы по краям,
- переводит имя в верхний регистр,
- и добавляет восклицательный знак в конце.
Пример использования:
names := []string{" alice ", "Bob", " charlie "}
res := transform(names, func(s string) string {
// ...
})
fmt.Println(res) // [ALICE! BOB! CHARLIE!]
Попробуйте также вызвать transform
с другой анонимной функцией — например, чтобы посчитать длину строк.
Показать решение
package main
import (
"fmt"
"strings"
)
func transform(xs []string, f func(string) string) []string {
res := make([]string, len(xs))
for i, v := range xs {
res[i] = f(v)
}
return res
}
func main() {
names := []string{" alice ", "Bob", " charlie "}
excited := transform(names, func(s string) string {
s = strings.TrimSpace(s)
s = strings.ToUpper(s)
return s + "!"
})
fmt.Println(excited) // [ALICE! BOB! CHARLIE!]
}
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.