Go: Функции

Теория: Функции как возвращаемое значение

В 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» и так далее.

Где это реально применяется

  1. Настраиваемые обработчики. В веб-фреймворках часто нужно вернуть функцию-обработчик HTTP-запроса, в которую уже «зашиты» какие-то данные или настройки.
  2. Фабрики и конструкторы. Когда нужно вернуть готовый «инструмент» с определённым поведением.
  3. Инкапсуляция состояния. Счётчики, кэши, трекеры — всё это удобно делать через возвращаемые функции.
  4. Декораторы. Можно возвращать новую функцию, которая оборачивает старую, добавляя проверку, логирование или обработку ошибок.

Функции как возвращаемое значение — это способ создавать новые функции «на лету». Мы можем один раз описать, как они должны работать, и потом получать разные варианты с нужными настройками. Это даёт гибкость: можно хранить внутри функции состояние, можно заранее фиксировать параметры, можно возвращать функции-обработчики прямо из фабрик.

Вместо того чтобы писать кучу повторяющегося кода, мы создаём функцию, которая делает функции. Это и есть главное удобство.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845