Как в Go передать функцию в качестве аргумента в другую функцию? (Функции высшего порядка)

Ответ

В Go функции являются гражданами первого класса (first-class citizens). Это означает, что их можно присваивать переменным, передавать в качестве аргументов другим функциям и возвращать из них. Функции, которые принимают другие функции в качестве аргументов или возвращают их, называются функциями высшего порядка (higher-order functions).

Способы передачи функции как параметра

Существует два основных способа это сделать:

1. Прямое указание сигнатуры функции

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

package main

import "fmt"

// process принимает число и функцию, которая будет это число обрабатывать.
// Тип функции-аргумента: func(int) int (принимает int, возвращает int)
func process(value int, action func(int) int) int {
    fmt.Printf("Выполняем действие над числом %dn", value)
    return action(value)
}

// double - конкретная функция, соответствующая сигнатуре func(int) int
func double(n int) int {
    return n * 2
}

func main() {
    // Передаем именованную функцию 'double'
    result1 := process(5, double)
    fmt.Printf("Результат: %dnn", result1) // Результат: 10

    // Передаем анонимную функцию прямо в вызове
    result2 := process(5, func(n int) int {
        return n * n
    })
    fmt.Printf("Результат: %dn", result2) // Результат: 25
}

2. Использование именованного типа функции

Для улучшения читаемости и переиспользования сложных сигнатур можно объявить собственный тип функции с помощью ключевого слова type.

package main

import "fmt"

// объявляем собственный тип для функции, которая принимает и возвращает int
type IntOperation func(int) int

// applyOperation использует наш новый тип для большей ясности
func applyOperation(value int, op IntOperation) int {
    return op(value)
}

func increment(n int) int {
    return n + 1
}

func decrement(n int) int {
    return n - 1
}

func main() {
    // Теперь мы можем передавать любые функции, 
    // которые соответствуют сигнатуре IntOperation
    fmt.Println(applyOperation(10, increment)) // 11
    fmt.Println(applyOperation(10, decrement)) // 9
}

Практическое применение

  • Middleware в веб-серверах: Обработчики HTTP-запросов часто оборачиваются в цепочку функций (middleware) для логирования, аутентификации, сжатия и т.д.
  • Callbacks: Для выполнения кода после завершения асинхронной операции.
  • Реализация паттерна "Стратегия": Возможность подменять алгоритм выполнения на лету.
  • Обработка данных: Написание универсальных функций для работы со слайсами (например, filter, map), которые принимают функцию-предикат.

Ответ 18+ 🔞

А, слушай, смотри, в Go функции — это как бы не просто куски кода, а прям полноправные граждане, понимаешь? Граждане первого класса, блядь! Это значит, что с ними можно делать всё, что и с любой другой переменной: запихнуть в переменную, сунуть как аргумент в другую функцию или выдать на выходе. А функции, которые принимают другие функции или выплёвывают их обратно, зовутся функциями высшего порядка. Звучит пафосно, но суть простая, ёпта.

Как запихнуть функцию как параметр? Есть два основных пути.

1. Прям в лоб, указав сигнатуру

Можно прямо в аргументах написать, какую именно функцию ты ждёшь. Типа, вот тебе число, а вот тебе инструкция, что с ним делать.

package main

import "fmt"

// process ждёт число и функцию, которая это число обработает.
// Тип этой функции-аргумента: func(int) int (глотает int, отрыгивает int)
func process(value int, action func(int) int) int {
    fmt.Printf("Выполняем действие над числом %dn", value)
    return action(value)
}

// double — конкретная функция, которая вписывается в шаблон func(int) int
func double(n int) int {
    return n * 2
}

func main() {
    // Передаём именованную функцию 'double'
    result1 := process(5, double)
    fmt.Printf("Результат: %dnn", result1) // Результат: 10

    // А можно и анонимную функцию прямо на месте состряпать и запихнуть
    result2 := process(5, func(n int) int {
        return n * n
    })
    fmt.Printf("Результат: %dn", result2) // Результат: 25
}

2. Через свой, блядь, именованный тип

Чтобы не писать эти длинные сигнатуры каждый раз и код был читаемее, можно объявить свой собственный тип для функции. Типа, дать ей паспорт.

package main

import "fmt"

// Объявляем свой тип для функции, которая работает с int.
type IntOperation func(int) int

// applyOperation использует наш новый тип. Сразу видно, чего ждёт.
func applyOperation(value int, op IntOperation) int {
    return op(value)
}

func increment(n int) int {
    return n + 1
}

func decrement(n int) int {
    return n - 1
}

func main() {
    // Теперь можно пихать любые функции, которые подходят под тип IntOperation
    fmt.Println(applyOperation(10, increment)) // 11
    fmt.Println(applyOperation(10, decrement)) // 9
}

А где это, блядь, применить-то?

Да везде, ёпта! Это ж мощнейший инструмент.

  • Middleware в веб-серверах: Обработчики запросов оборачиваются в цепочку таких функций-обёрток для логирования, проверки прав и прочей хуйни. Красота, а не жизнь.
  • Коллбэки: Чтобы что-то выполнить, когда какая-то долгая операция наконец-то, сука, завершится.
  • Паттерн "Стратегия": Менять алгоритм выполнения прямо на ходу, как перчатки.
  • Обработка данных: Писать универсальные утилиты для слайсов (типа filter, map), которым ты просто говоришь: "Вот тебе список, а вот тебе правило, как с каждым элементом поступить". И они, блядь, работают.