β

Как стать автором
Курсы по фронтенду
Айтишники, аттеншн! Выберите лучший IT-бренд 2025

Моя лентаВсе потоки

538.49

Рейтинг

OTUS
Развиваем технологии, обучая их создателей

Подписаться

MaxRokatansky 18 ноя 2019 в 15:47

Функциональная парадигма на Go: основные техники

5 мин

15K

Блог компании OTUSGo*

Всем привет, напоминаем о том, что в этом месяце в OTUS стартует новый набор по курсу «Разработчик Golang». Несмотря на некоторый хейт предыдущей статьи по Golang, наш внештатный автор решил рискнуть продолжить серию статей, посвященных этому языку. Мы попробуем пройти по этому тонкому льду еще раз, оперевшись на то, на что в Golang вроде как можно опереться — на функциональную парадигму.

Напоминаем, что данная статья является неким материалом для «внеклассного чтения» и не имеет отношения к программе курса, с которой можно ознакомиться тут.

Понятно, что у профессиональных программистов на других языках Golang вызывает

раздражение — вроде бы взрослый компилируемый язык, однако понятие классов и наследования отсутствует в принципе (хотя, ООП в языке реализуется, пускай и достаточно непривычным способом, через систему структов и интерфейсов). Однако сегодня мы посмотрим на основные реализации привычных конструкций в функциональной парадигме и постараемся объяснить и их, и сам синтаксис языка.

Сейчас достаточно много хайпа вокруг функциональной парадигмы (FP). Однако она тоже не является панацеей от всех проблем, и тоже имеет свои плюсы и минусы.

Кратко о том, что же такое функциональная парадигма

Функциональная парадигма пришла в программирование из математики. Она формирует следующие требования к программе:

Отсутствие изменений в уже имеющихся данных.

Нет скрытого состояния.

Что это нам дает?

Наши функции работают без сторонних эффектов. Иными словами, функция должна только возвращать значение и не должна влиять на какие-то внешние данные.

Использование чистых функций. Они повышают ретестовую надежность функций вне зависимости от приходящих данных — иными словами, программы становятся более надежными для тестирования и результаты их работы становятся более предсказуемыми.

Итак, какие возможности имеет Golang для реализации функциональной парадигмы:

Функции первого класса

Функции первого класса есть во многих языках программирования. Читатель этой статьи скорее всего уже знает их концепцию из столь распространенного JavaScript, но я повторю ещё раз. Функции первого класса (high order function) это функции, которые могут возвращать в качестве знания другую функцию, принимать в качестве аргумента функцию, и передавать значение функции другой переменной.

Давайте с самого начала условимся: чтобы сэкономить место, я выкинул из кода, который здесь представлен, две первые строчки: 'package main' и импорт 'import «fmt»'. Но для запуска кода на вашей машине не забудьте их добавить).

func main() {
var list = []int{15, 16, 45, 34}
// здесь мы получаем массив чисел
var out = forEach(list, func(it int) int { // принимает на вход наш массив
//forEach нам пришлось "Эмулировать" самостоятельно
return (it * it) // возвращает степень каждого отдельного элемента
})
fmt.Println(out) // [225, 256, 2025, 1156]
fmt.Println(list) // наш изначальный массив остался нетронутым

}

func forEach(arr []int, fn func(it int) int) []int {
// принимает на вход массив с числами, функцию с числом, и вид возвращаемого типа
var newArray = []int{} // создаем новый массив для "неизменяемости" наших данных
for _, it := range arr {
newArray = append(newArray, fn(it)) // нам все равно приходится воспользоваться for
}
return newArray
}

На самом деле вовсе не обязательно самостоятельно придумывать с нуля свой map или foreach. Есть множество библиотек, которые это реализуют, остается только их подключить. К примеру, вот эта.

Замыкания и карринг функций

Замыкания есть во множестве современных языков программирования. Замыкания — это функция, которая ссылается на свободные переменные области видимости своей родительской функции. Карринг функции — это изменение функции от вида func(a,b,c) до вида func(a)(b)(c).

Приведем пример замыканий и карринга в Go:

//пример замыкания
func multiply(x int) func(y int) int { //именованная и анонимная функция
return func(y int) int { // классическое возвращение функции, если вы это видели например в JS
return x * y
}
}

func main() {
//приведем пару примеров каррирования функции
var mult10 = multiply(10)
var mult15 = multiply(15)

fmt.Println(mult10(5)) //50
fmt.Println(mult15(15))//225
}

Чистые функции

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

Приведем пример неудачной, «грязной» функции:

var arrToSave = map[string]int{} //map - это неупорядочненные ключ - значения в Golang

func dirtySum(a, b int) int {
c := a + b
arrToSave[fmt.Sprintf("%d", a, b)] = c
//кто не в курсе, аргумент "%d" - это нужно для вывода десятичного числа
return c
}

Здесь у нас функция должна принимать работать максимально предсказуемо:

func simpleSum(x, y int) int {
return x + y
}

func main() {
fmt.Printf("%v", dirtySum(13, 12))

//сейчас две эти переменные сработают одинаково
// но у "грязной" результат выполнения куда менее предсказуем
fmt.Printf("%v", simpleSum(13, 12))
}

«Заходит как-то рекурсия в бар, и больше в бар никто не заходит»

Из сборника несмешных анекдотов.

Рекурсия

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

Приведем пример вычисления факториала с помощью императивной и декларативной парадигмы:

func funcFactorial(num int) int {
if num == 0 {
return 1
}
return num * funcFactorial(num-1)
}

func imperativeFactorial(num int) int {
var result int = 1
for ; num > 0; num-- { //тут у нас привычный for
result *= num
}
return result
}

func main() {
fmt.Println(funcFactorial(20)) // как вы догадываетесь результаты тут будут одинаковые
fmt.Println(imperativeFactorial(20)) //однако дело в скорости вычисления функций
}

Сейчас функция рекурсии работает достаточно неэффективно. Попробуем её немного переписать, что бы оптимизировать скорость её вычисления:

func factTailRec(num int) int {
return factorial(1, num) // отдельная функция на "хвост" функции
}

func factorial(accumulator, val int) int {
if val == 1 {
return accumulator
}
return factorial(accumulator*val, val-1)
}

func main() {
fmt.Println(factTailRec(20)) // 2432902008176640000
}

Наша скорость вычисления факториала незначительно возросла. Бенчмарки приводить не буду).

В Go к сожалению не реализована оптимизация рекурсии из «коробки», поэтому хвост рекурсии приходится оптимизировать самостоятельно. Хотя, вне сомнения, наверняка может найтись полезная библиотека на данную тему. Например, есть вот такая «Loadash для Golang» классная на эту тему.

Ленивые вычисления

В теории программирования, ленивые вычисления (так же известные как «отложенные вычисления») это процесс откладывания вычисления до того момента, пока это не потребуется. В Golang пока нет поддержки ленивого вычисления прямо «из коробки» поэтому мы можем только засимулировать это:

func mult(x, y int) int {
fmt.Println("выполняем умножение")
return x * x.
}

func divide(x, y int) int {
fmt.Println("выполняем деление")
return x / y
//тут не вижу смысла что-то комментировать
}

func main() {
fmt.Println(multOrDivide(true, mult, divide, 17, 3)) //здесь вызываем наши "эмулирующую" ленивые вычисления функцию, которая через 1 аргумент решает,
// пора ли уже вызывать вычисления функции или нет
fmt.Println(multOrDivide(false, mult, divide, 17, 3))
}

// наш if - else откладывает выполнение наших "одиночных" функций
func multOrDivide(add bool, onMult, onDivide func(t, z int) int, t, z int) int {
if add {
return onMult(t, z)
}
return onDivide(t, z)
}

Чаще всего «эмулированные» ленивые выражения не стоят того, потому что чрезмерно усложняют код, но если ваши функции достаточно тяжелы в управлении, тогда стоит воспользоваться и этим методом. Но вы можете обратиться к другим решения, например, к этим .

На этом все. У нас получилось только введение в функциональную парадигму на Golang. К сожалению, часть возможностей пришлось засимулировать. Часть, вполне развитые функциональные приемы, такие как монады, сюда не вошли, потому что статей о них в Go на хабре полно. В самом языке ещё многое может улучшиться, например со следующий большой версией (GO 2) в языке ожидается появление генериков. Что ж, будем ждать и надеяться).

Теги:

go

функции

Хабы:

Блог компании OTUS

Go

+7

79

26
+26

OTUS
Развиваем технологии, обучая их создателей

Подписаться

СайтВКонтактеTelegram

129

Карма

40.8

Рейтинг

OTUS @MaxRokatansky

Редактор

Подписаться

Комментарии 26
+26

Публикации

Лучшие за суткиПохожие

A_Tsymbalyuk 13 часов назад

Физика ошибок не прощает. Она догонит тебя на крыше и заставит платить (дважды)

Простой

6 мин

8.1K

Кейс

+40

35

25
+25

BabayMazay 12 часов назад

Самодельные французские радиолампы 1920 г. Часть 3. Разрядная трубка, лампа накаливания

Средний

9 мин

1.5K

Ретроспектива

+33

9

11
+11

Bright_Translate 8 часов назад

Как я от нечего делать замедлил Postgres в 42 000 раз

Средний

11 мин

2.6K

Кейс

Перевод

+27

19

2
+2

lokosst 12 часов назад

Выделенные серверы + Kubernetes: практическое руководство

17 мин

2.4K

Туториал

+21

23

0

butivskiydm 11 часов назад

Моё мини-исследование про продолжительность жизни в России

Простой

2 мин

6.3K

Мнение

+19

17

59
+59

JetHabr 11 часов назад

«Плохие парни работают просто»: пентестеры разбирают реальные кейсы

Средний

9 мин

2.1K

Кейс

+17

19

1
+1

k0mar0v 14 часов назад

HTC Desire HD: история, дизайн и возможности легендарного смартфона

6 мин

2.6K

+17

5

5
+5

MaFrance351 13 часов назад

Домофон Лидер-М. Как скрестить трубку с ключом и всем проиграть

Простой

4 мин

2.5K

+16

5

6
+6

psygalaxy 13 часов назад

Как мы ускорили проверку документации с помощью AI-агента: от боли к решению

Средний

25 мин

1.8K

Обзор

+16

29

9
+9

kubelet 16 часов назад

DNS-хаос, зомби-поды и майнеры в кластере: самые невероятные случаи при работе с Kubernetes

Простой

14 мин

1.2K

Кейс

+16

23

1
+1

AI и майндсет айтишников — выясняем, как изменились IT-работодатели за год
Турбо

Показать еще

Минуточку внимания

Событие

Что ни день – то ивент, если следить за Календарём

Событие

Как построить IT-бренд через вирусный контент и юмор

Опрос

Да начнётся битва: выбираем лучший IT-бренд работодателя

Курсы

PRO Go. Базовый курс по структурам данных
По желаниюStepik

Python. Часть 4 (Функции, замыкание и декораторы)
По желаниюStepik

Магистратура Разработка IT-продукта
По факту набораНетология

Go-разработчик
По факту набораБруноям

Проектирование микросервисной архитектуры
По факту набораСлёрм

Больше курсов на Хабр Карьере

Информация

Сайт

otus.ru

Дата регистрации

22 марта 2017

Дата основания

1 апреля 2017

Численность

101–200 человек

Местоположение

Россия

Представитель

OTUS

Виджет

ВКонтакте

Блог на Хабре

4 часа назад

Модель 3–3–3: как прогнозировать скорость команды и не попадать в цейтнот

395

0

8 часов назад

Всё про std::search и где его применять

1.4K

3
+3

12 часов назад

Три ошибки в C#, которые лучше не допускать

1.3K

2
+2

16 часов назад

Какие тренды должен учитывать CTO в 2025 году

1.4K

2
+2

31 июл в 19:49

Я перешла из Data Science в AI Engineering: вот всё, что вам нужно знать

1.6K

0

Истории

История Яндекс Почты

С Днём сисадмина!

Как расти в ИТ: советы, гайды и опыт сеньоров

1 055 450 очков в облаке – попробуй обогнать

Годнота из блогов компаний

Реклама

АО СЕЛЕКТЕЛ

ИНН 7810962785

erid: 2VSb5zDBFAH

1 000 000 стартапам

Кешбэк 30% на год в Selectel!

Ваш аккаунт

Профиль

Трекер

Диалоги

Настройки

ППА

Разделы

Статьи

Новости

Хабы

Компании

Авторы

Песочница

Информация

Устройство сайта

Для авторов

Для компаний

Документы

Соглашение

Конфиденциальность

Услуги

Корпоративный блог

Медийная реклама

Нативные проекты

Образовательные программы

Стартапам

Настройка языкаТехническая поддержка
© 2006–2025, Habr