- Возврат функции с параметрами
- Возврат функции с внутренним состоянием
- Ещё пример: фабрика умножителей
- Где это реально применяется
В Go функция — это значение. Точно так же, как переменной можно присвоить число, строку или структуру, переменной можно присвоить и функцию. Раз функция — значение, значит, её можно вернуть из другой функции.
Что значит «возвращать функцию»? Это значит, что в return
мы можем не число, строку или структуру вернуть, а саму функцию. На первый взгляд звучит странно, но на деле это очень удобно: мы один раз описываем, как именно должна работать новая функция, и потом получаем её «на руки» в виде результата.
Сделаем функцию, которая просто возвращает другую функцию, печатающую сообщение.
package main
import "fmt"
// makePrinter возвращает функцию, которая печатает "Hello!"
func makePrinter() func() {
return func() {
fmt.Println("Hello!")
}
}
func main() {
printer := makePrinter() // получили функцию
printer() // вызвали её
printer() // можем вызывать сколько угодно
}
Здесь makePrinter
вернула функцию без имени (func() { ... }
). Эта функция умеет печатать строку. Мы сохранили её в переменной printer
и теперь можем вызывать как обычную функцию.
Возврат функции с параметрами
Можно сделать и посложнее: возвращаемая функция может принимать аргументы. Например, пусть мы заранее «сконфигурируем» приветствие, а потом просто будем передавать имя.
package main
import "fmt"
// greeter возвращает функцию, которая приветствует с заданным приветствием
func greeter(greeting string) func(string) {
return func(name string) {
fmt.Printf("%s, %s!\n", greeting, name)
}
}
func main() {
hello := greeter("Привет") // функция с фиксированным "Привет"
bonjour := greeter("Bonjour") // функция с фиксированным "Bonjour"
hello("Иван") // Привет, Иван!
bonjour("Marie") // Bonjour, Marie!
}
Смысл такой: функция greeter
«запоминает» слово приветствия и возвращает функцию, которая его использует. В итоге мы получаем разные функции — hello
и bonjour
— каждая со своим заранее выбранным поведением.
Возврат функции с внутренним состоянием
Иногда хочется не только вернуть функцию, но и чтобы она хранила у себя какое-то состояние. Это очень полезно. Например, сделаем счётчик.
package main
import "fmt"
// makeCounter возвращает функцию-счётчик
func makeCounter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
counterA := makeCounter()
counterB := makeCounter()
fmt.Println(counterA()) // 1
fmt.Println(counterA()) // 2
fmt.Println(counterA()) // 3
fmt.Println(counterB()) // 1 — у второго свой счётчик
}
Здесь переменная count
не пропадает после выхода из makeCounter
, потому что возвращаемая функция продолжает её использовать. Это называется замыкание. У каждого вызова makeCounter
будет своё собственное хранилище count
.
Ещё пример: фабрика умножителей
Допустим, нам часто нужно умножать числа на разные коэффициенты. Вместо того чтобы всё время писать x * 2
, x * 3
, x * 10
, мы можем создать «фабрику функций»:
package main
import "fmt"
// multiplier возвращает функцию, которая умножает число на factor
func multiplier(factor int) func(int) int {
return func(x int) int {
return x * factor
}
}
func main() {
double := multiplier(2) // функция "умножить на 2"
triple := multiplier(3) // функция "умножить на 3"
fmt.Println(double(5)) // 10
fmt.Println(triple(5)) // 15
}
Мы заранее фиксируем множитель, а дальше получаем готовые функции «умножитель на 2», «умножитель на 3» и так далее.
Где это реально применяется
- Настраиваемые обработчики. В веб-фреймворках часто нужно вернуть функцию-обработчик HTTP-запроса, в которую уже «зашиты» какие-то данные или настройки.
- Фабрики и конструкторы. Когда нужно вернуть готовый «инструмент» с определённым поведением.
- Инкапсуляция состояния. Счётчики, кэши, трекеры — всё это удобно делать через возвращаемые функции.
- Декораторы. Можно возвращать новую функцию, которая оборачивает старую, добавляя проверку, логирование или обработку ошибок.
Функции как возвращаемое значение — это способ создавать новые функции «на лету». Мы можем один раз описать, как они должны работать, и потом получать разные варианты с нужными настройками. Это даёт гибкость: можно хранить внутри функции состояние, можно заранее фиксировать параметры, можно возвращать функции-обработчики прямо из фабрик.
Вместо того чтобы писать кучу повторяющегося кода, мы создаём функцию, которая делает функции. Это и есть главное удобство.
Самостоятельная работа
Потренируемся создавать функции, которые возвращают другие функции и сохраняют внутреннее состояние между вызовами.
Реализуйте функцию makeAverager
, которая возвращает функцию для вычисления среднего значения всех переданных чисел.
Каждый вызов возвращаемой функции добавляет новое число в набор и возвращает текущее среднее.
Пример использования:
avg := makeAverager()
fmt.Println(avg(10)) // 10.0
fmt.Println(avg(20)) // 15.0
fmt.Println(avg(30)) // 20.0
Подсказки:
- Используйте замыкание: внутри сохраняйте сумму и количество элементов.
- Не храните все числа в срезе — достаточно аккумулировать сумму и счётчик.
Показать решение
package main
import "fmt"
func makeAverager() func(float64) float64 {
var sum float64
var count int
return func(x float64) float64 {
count++
sum += x
return sum / float64(count)
}
}
func main() {
avg := makeAverager()
fmt.Println(avg(10)) // 10.0
fmt.Println(avg(20)) // 15.0
fmt.Println(avg(30)) // 20.0
}
Дополнительные материалы
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.