गो में फलन
Функции в Go: множественный возврат и замыкания
Функции в Go имеют уникальную суперспособность: они могут возвращать сразу несколько значений! Это то, чего нет в большинстве языков. Именно это позволяет Go обрабатывать ошибки так элегантно. Давай разберём всё о функциях в Go.
Базовый синтаксис функций
package main
import "fmt"
// func Имя(параметры) возвращаемыйТип { ... }
func greet(name string) string {
return "Привет, " + name + "!"
}
func add(a, b int) int {
return a + b
}
// Несколько параметров одного типа — можно сократить
func multiply(a, b int) int { // вместо (a int, b int)
return a * b
}
// Без возвращаемого значения
func printLine(text string) {
fmt.Println(text)
}
func main() {
msg := greet("Мир")
fmt.Println(msg) // Привет, Мир!
result := add(3, 5)
fmt.Println(result) // 8
printLine("Привет!")
}
Множественный возврат: суперсила Go
Самая уникальная черта функций Go — возможность возвращать несколько значений. Это используется повсеместно, особенно для возврата результата и ошибки одновременно.
package main
import (
"fmt"
"errors"
)
// Функция возвращает ДВА значения: результат и ошибку
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("деление на ноль")
}
return a / b, nil // nil означает "нет ошибки"
}
// Три возвращаемых значения
func minMax(nums []int) (min, max, sum int) {
min, max, sum = nums[0], nums[0], 0
for _, n := range nums {
if n < min { min = n }
if n > max { max = n }
sum += n
}
return min, max, sum
}
func main() {
// Принимаем оба значения
result, err := divide(10, 3)
if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Printf("10 / 3 = %.4f\n", result) // 3.3333
}
// Ошибочный случай
result2, err2 := divide(5, 0)
if err2 != nil {
fmt.Println("Ошибка:", err2) // деление на ноль
}
_ = result2 // подавляем предупреждение
// Три значения
nums := []int{3, 1, 7, 2, 9, 4}
mn, mx, sm := minMax(nums)
fmt.Printf("Мин=%d, Макс=%d, Сумма=%d\n", mn, mx, sm)
}
Именованные возвращаемые значения
package main
import "fmt"
// Именованные возвращаемые значения
func divide2(a, b float64) (result float64, err error) {
if b == 0 {
err = fmt.Errorf("деление на ноль")
return // "голый" return — возвращает именованные переменные
}
result = a / b
return // возвращает result и err
}
// Удобно для документирования
func parseUserData(data string) (name string, age int, valid bool) {
// ...логика парсинга...
name = "Алиса"
age = 25
valid = true
return
}
func main() {
r, e := divide2(10, 4)
fmt.Println(r, e) // 2.5
}
Variadic функции: переменное количество аргументов
package main
import "fmt"
// ... перед типом — принимаем любое количество аргументов
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func printAll(sep string, items ...string) {
for i, item := range items {
if i > 0 {
fmt.Print(sep)
}
fmt.Print(item)
}
fmt.Println()
}
func main() {
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
fmt.Println(sum()) // 0
// Передача среза в variadic
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60 (... разворачивает срез)
printAll(", ", "Яблоко", "Банан", "Вишня")
// Яблоко, Банан, Вишня
}
Функции как значения: первоклассные функции
В Go функции — это значения первого класса. Это значит, что функцию можно сохранить в переменную, передать как аргумент и вернуть из функции.
package main
import "fmt"
func main() {
// Функция в переменной
square := func(x int) int {
return x * x
}
fmt.Println(square(5)) // 25
// Функция как аргумент
apply := func(nums []int, fn func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = fn(n)
}
return result
}
nums := []int{1, 2, 3, 4, 5}
squared := apply(nums, square)
fmt.Println(squared) // [1 4 9 16 25]
doubled := apply(nums, func(x int) int { return x * 2 })
fmt.Println(doubled) // [2 4 6 8 10]
// Тип функции
type MathFunc func(int, int) int
var add MathFunc = func(a, b int) int { return a + b }
var mul MathFunc = func(a, b int) int { return a * b }
operations := map[string]MathFunc{
"add": add,
"mul": mul,
}
fmt.Println(operations["add"](3, 4)) // 7
fmt.Println(operations["mul"](3, 4)) // 12
}
Замыкания (Closure)
Замыкание — это функция, которая «захватывает» переменные из внешней области видимости. Представь: функция «помнит» контекст, в котором была создана.
package main
import "fmt"
// Функция, возвращающая замыкание
func makeCounter() func() int {
count := 0 // Эта переменная "захватывается" замыканием
return func() int {
count++ // Каждый вызов увеличивает count
return count
}
}
func makeAdder(x int) func(int) int {
return func(y int) int {
return x + y // x "захвачен" из внешней функции
}
}
func main() {
// Создаём счётчик
counter := makeCounter()
fmt.Println(counter()) // 1
fmt.Println(counter()) // 2
fmt.Println(counter()) // 3
// Независимые счётчики!
counter2 := makeCounter()
fmt.Println(counter2()) // 1 (не 4!)
fmt.Println(counter()) // 4 (продолжает свой счёт)
// Создаём функции-сложения
add5 := makeAdder(5)
add10 := makeAdder(10)
fmt.Println(add5(3)) // 8
fmt.Println(add10(3)) // 13
fmt.Println(add5(7)) // 12
// Практичный пример: генератор fibonacci
fib := func() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}()
for i := 0; i < 8; i++ {
fmt.Print(fib(), " ")
}
// 1 1 2 3 5 8 13 21
}
Рекурсия
package main
import "fmt"
// Рекурсивный факториал
func factorial(n int) int {
if n <= 1 {
return 1
}
return n * factorial(n-1)
}
// Числа Фибоначчи
func fibonacci(n int) int {
if n <= 1 {
return n
}
return fibonacci(n-1) + fibonacci(n-2)
}
func main() {
fmt.Println(factorial(5)) // 120
fmt.Println(factorial(10)) // 3628800
for i := 0; i < 10; i++ {
fmt.Print(fibonacci(i), " ")
}
// 0 1 1 2 3 5 8 13 21 34
}
init функция: специальная инициализация
package main
import "fmt"
var config map[string]string
// init вызывается автоматически при запуске пакета
func init() {
config = make(map[string]string)
config["host"] = "localhost"
config["port"] = "8080"
fmt.Println("Конфигурация загружена")
}
func main() {
fmt.Println("Программа запущена")
fmt.Println("Host:", config["host"])
}
// Конфигурация загружена
// Программа запущена
// Host: localhost
Итого
- ✅ Функции объявляются через
func - ✅ Множественный возврат —
return result, err— уникальная фича Go - ✅ Именованные возвращаемые значения — удобны для документирования
- ✅ Variadic —
nums ...int— переменное количество аргументов - ✅ Функции первого класса — можно хранить в переменных и передавать как аргументы
- ✅ Замыкания — функции, захватывающие переменные из внешней области
- ✅ init() — автоматически вызывается при инициализации пакета
Анонимные функции и горячие вызовы (IIFE)
package main
import "fmt"
func main() {
// Анонимная функция — объявляем и сразу вызываем (IIFE)
result := func(a, b int) int {
return a + b
}(3, 4)
fmt.Println(result) // 7
// Анонимная в горутине — часто используется
go func() {
fmt.Println("Это горутина")
}()
// Функция в переменной
greet := func(name string) string {
return "Привет, " + name + "!"
}
fmt.Println(greet("Мир")) // Привет, Мир!
}
Паттерн функционального программирования
package main
import "fmt"
// map, filter, reduce для срезов
func mapInts(nums []int, f func(int) int) []int {
result := make([]int, len(nums))
for i, n := range nums {
result[i] = f(n)
}
return result
}
func filter(nums []int, pred func(int) bool) []int {
var result []int
for _, n := range nums {
if pred(n) { result = append(result, n) }
}
return result
}
func reduce(nums []int, initial int, f func(int, int) int) int {
acc := initial
for _, n := range nums {
acc = f(acc, n)
}
return acc
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
doubled := mapInts(nums, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6 8 10 12 14 16 18 20]
evens := filter(nums, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4 6 8 10]
sum := reduce(nums, 0, func(acc, n int) int { return acc + n })
fmt.Println(sum) // 55
// Цепочка: сумма квадратов чётных чисел
result := reduce(
mapInts(filter(nums, func(n int) bool { return n%2 == 0 }),
func(n int) int { return n * n }),
0,
func(acc, n int) int { return acc + n })
fmt.Println(result) // 4+16+36+64+100 = 220
}
Обработка ошибок в функциях
package main
import (
"errors"
"fmt"
"strconv"
)
// Идиоматичная функция Go возвращает (result, error)
func parseAndDouble(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("parseAndDouble: не могу конвертировать %q: %w", s, err)
}
return n * 2, nil
}
// Цепочка ошибок
func processInput(input string) (string, error) {
doubled, err := parseAndDouble(input)
if err != nil {
return "", fmt.Errorf("processInput: %w", err)
}
return fmt.Sprintf("Результат: %d", doubled), nil
}
func main() {
// Успешный вызов
result, err := processInput("21")
if err != nil {
fmt.Println("Ошибка:", err)
} else {
fmt.Println(result) // Результат: 42
}
// Неуспешный вызов
_, err = processInput("abc")
fmt.Println("Ошибка:", err)
// Ошибка: processInput: parseAndDouble: не могу конвертировать "abc": ...
// Проверка вложенных ошибок
var numErr *strconv.NumError
if errors.As(err, &numErr) {
fmt.Println("Это ошибка числа:", numErr.Num)
}
}
Итого
- ✅ Функции —
func name(params) returnType { ... } - ✅ Множественный возврат:
return result, err— главная фича Go - ✅ Именованные возвращаемые значения — для документирования и голого return
- ✅ Variadic:
nums ...int— переменное число аргументов - ✅ Функции первого класса — в переменных, как аргументы, как возврат
- ✅ Замыкания — функции, «помнящие» переменные из внешней области
- ✅ init() — инициализация пакета при загрузке
- ✅ Ошибки возвращаются как обычные значения, не как исключения
Дженерики в Go (Go 1.18+)
package main
import (
"cmp"
"fmt"
)
// Обобщённая функция работает с любым comparable типом
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item { return true }
}
return false
}
// Обобщённая функция с ограничением ordered
func Min[T cmp.Ordered](a, b T) T {
if a < b { return a }
return b
}
func Max[T cmp.Ordered](a, b T) T {
if a > b { return a }
return b
}
// Обобщённая Map функция
func Map[T, U any](slice []T, f func(T) U) []U {
result := make([]U, len(slice))
for i, v := range slice { result[i] = f(v) }
return result
}
// Обобщённый Filter
func Filter[T any](slice []T, pred func(T) bool) []T {
var result []T
for _, v := range slice {
if pred(v) { result = append(result, v) }
}
return result
}
func main() {
// int
ints := []int{1, 2, 3, 4, 5}
fmt.Println(Contains(ints, 3)) // true
fmt.Println(Contains(ints, 10)) // false
fmt.Println(Min(5, 3)) // 3
// string
words := []string{"Go", "Python", "Rust"}
fmt.Println(Contains(words, "Go")) // true
fmt.Println(Max("Banana", "Apple")) // Banana
// Map и Filter
doubled := Map(ints, func(n int) int { return n * 2 })
fmt.Println(doubled) // [2 4 6 8 10]
evens := Filter(ints, func(n int) bool { return n%2 == 0 })
fmt.Println(evens) // [2 4]
// Преобразование типов
strs := Map(ints, func(n int) string { return fmt.Sprintf("#%d", n) })
fmt.Println(strs) // [#1 #2 #3 #4 #5]
}
Практика: цепочка обработки
package main
import (
"fmt"
"strings"
"unicode"
)
// Тип для функции-трансформера строки
type StringTransform func(string) string
// Применить цепочку трансформаций
func Pipeline(s string, transforms ...StringTransform) string {
for _, t := range transforms {
s = t(s)
}
return s
}
func main() {
// Набор трансформаций
trim := func(s string) string { return strings.TrimSpace(s) }
lower := func(s string) string { return strings.ToLower(s) }
removeNums := func(s string) string {
return strings.Map(func(r rune) rune {
if unicode.IsDigit(r) { return -1 }
return r
}, s)
}
input := " Hello123 WORLD456! "
result := Pipeline(input, trim, lower, removeNums)
fmt.Printf("Input: %q\n", input)
fmt.Printf("Output: %q\n", result)
// Output: "hello world!"
// Каррирование
multiplier := func(factor int) func(int) int {
return func(n int) int { return n * factor }
}
double := multiplier(2)
triple := multiplier(3)
nums := []int{1, 2, 3, 4, 5}
for _, n := range nums {
fmt.Printf("%d×2=%d, %d×3=%d\n", n, double(n), n, triple(n))
}
}