🐹 Go

गो में फलन

Функции в 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))
    }
}