Практика функционального программирования в Go
16 марта 2022 г.Работая с новым языком, вы всегда будете пытаться найти сходство с уже известными вам языками. У меня есть опыт работы с JavaScript и Java, и я могу сделать вывод, что Go больше похож на JS. Почему это так? Ну, на мой взгляд, это из-за поддержки некоторых функциональных особенностей парадигмы.
Чистые функции
Это базовая сущность функционального программирования. Из вики:
возвращаемые функции идентичны для идентичных аргументов
Вот пример чистой функции:
```иди
функция умножить (a, b int) int {
вернуть а * б
Функционирует как гражданин первого класса
Согласно вики, это означает, что:
данный объект (например, функция) поддерживает все операционные свойства, присущие другим объектам; такие свойства, как возможность присваиваться переменной, передаваться в качестве аргумента функции, возвращаться из функции и т. д.
Мы можем вернуть функцию:
```иди
основной пакет
импортировать "фмт"
основная функция () {
myFunc := makeCounter()
мояФункция()
мояФункция()
функция makeCounter() функция() {
счетчик := 0
функция возврата() {
fmt.Println(счетчик)
счетчик++
Здесь вы должны заметить, что функция makeCounter
не только возвращает новую анонимную функцию, но также имеет переменную, которая сохраняет лексическое замыкание анонимной функции, как в JavaScript.
В языках программирования замыкание, а также лексическое замыкание или замыкание функции – это метод реализации привязки имен с лексической областью действия в языке с первоклассными функциями. С точки зрения эксплуатации замыкание — это запись, хранящая функцию вместе с окружением.
Упаковка
На момент написания этой статьи Go не поддерживает универсальные типы до версии Go 1.18, которая будет включать поддержку универсальных типов и должна быть выпущена в марте 2022 г.
Go 1.18 еще не выпущен. Это незавершенные примечания к выпуску. Ожидается, что Go 1.18 будет выпущен в марте 2022 года.
Несмотря на это, вы также можете использовать некоторые функции-оболочки, например, если вы хотите предоставить некоторые данные для вывода при вызове определенных функций, например так:
```иди
функция умножить (a, b int) int {
вернуть а * б
func wrapWithLogs (fn func (a, b int) int) func (a, b int) int {
return func(a, b int) int {
начало := время.Сейчас()
г := fn(a, b)
продолжительность: = время. С (начало)
fmt.Println("функция выполнена", продолжительность)
вернуть р
Конечно, гораздо эффективнее будет использовать дженерики. Так что нам нужно просто немного подождать, пока новый релиз не будет опубликован :)
Если вы хотите получить имя функции динамически, вы также можете использовать:
```иди
время выполнения.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
Обтекание открывает двери для использования некоторых интересных вещей, таких как запоминание.
Мемоизация
В вычислительной технике мемоизация или мемоизация — техника оптимизации, используемая в основном для ускорения работы компьютерных программ за счет сохранения результатов дорогостоящих вызовов функций и возврата кэшированного результата при повторении тех же входных данных.
Как было сказано, если у вас есть какие-то тяжелые вычисления - всегда лучше где-то кэшировать уже вычисленные результаты и использовать их, а не выполнять те же вычисления снова.
```иди
func sum(a,b int) int {
вернуть а + б
func memo(fn func(a, b int) int) func(a, b int) int {
кеш: = сделать (карта [строка] целое)
return func(a, b int) int {
ключ := strconv.Itoa(a) + " " + strconv.Itoa(b)
v, хорошо := кэш[ключ]
если! хорошо {
fmt.Println("расчет...")
кеш[ключ] = fn(a, b)
v = кеш [ключ]
вернуть v
основная функция () {
mSum := памятка (сумма)
fmt.Println(mSum(2, 3))
fmt.Println(mSum(2, 3))
Результат будет:
:::Информация
расчет... 5 5
Конечно, вы можете избежать функции-обертки и написать логику запоминания прямо внутри вашей функции. Но все же вы будете использовать замыкание для хранения кеша внутри.
Рекурсия
Мы можем использовать рекурсию, если нам нужно вычислить что-то вроде факториала:
```иди
func funcFactorial(num int) int {
если число == 0 {
вернуть 1
вернуть число * funcFactorial (число-1)
Но, пожалуйста, помните об использовании рекурсии, когда это возможно. Проблема в том, что в большинстве случаев вместо рекурсии лучше использовать некий стек или очередь. Обычно пределы памяти лежат дальше, чем пределы стека вызовов.
Каррирование
Помните, мы обсуждали замыкание в первом примере? Каррирование — это получение параметра и его возврат в замыкании. Например, мы получили функцию умножения:
```иди
функция умножить (a, b int) int {
вернуть а * б
Мы можем назвать эту функцию как «умножить (2, 3)». С каррированием мы можем вызвать эту функцию как multiply(2)(3)
. Для этого нам нужно переписать нашу основную функцию:
```иди
функция умножить (a int) функция (b int) int {
функция возврата (b int) int {
вернуть а * б
Но переписывать существующую функцию только для использования каррирования — не лучшая практика. Используя некоторую функцию-оболочку, мы можем сделать это лучше:
```иди
функция умножить (a, b int) int {
вернуть а * б
func curry(a int) func(b int) int {
функция возврата (b int) int {
вернуть умножить (а, б)
Заключение
Go поддерживает некоторые функции функциональной парадигмы, чтобы упростить задачу. Но не забывайте, что это структурный язык, и нет необходимости пытаться писать код на Go в функциональном или ООП-стиле. Вы проиграете в обоих случаях.
Оригинал