Основы функционального программирования
Пару недель назад я наткнулся на статью о том, почему стоит обратить внимание на функциональное программирование. Прежде всего, в данной статье освещены основные особенности ФП, а именно:
- функции первого класса;
- функции высшего порядка;
- замыкания;
- иммутабельность состояний.
Стоит отметить, что Elixir — прекрасный способ постичь основы функционального программирования. Ну что, начнём?
Функции первого класса
Функции первого класса — это прежде всего те функции, которые можно присваивать переменным, передавать в качестве аргументов и вызывать из других функций. Если быть точным, в Elixir такие функции называются анонимными.
Рассмотрим пример:
Функции высшего порядка
Elixir позволяет не только присваивать функции переменным, но и передавать их другим функциям в виде аргументов. С точки зрения математики, функция высшего порядка — это такая функция, входными и выходными параметрами которой являются другие функции. Именно здесь Elixir предстаёт во всей своей красе. Конечно, функции высшего порядка можно реализовать и на других языках, но для Elixir они всё равно что живые клетки для человека. Предлагаю называть их функциями высшего порядка первого класса. :)
К примеру, давайте посмотрим вот на этот код:
В первой строке объявляется анонимная функция, она возводит число в квадрат и записывает значение в переменную square
. Затем идёт функция Enum.map
с двумя аргументами, первый из которых представляет собой последовательность чисел, а второй — функцию, применяемую к каждому элементу последовательности.
Замыкания
Для концепции замыканий характерны следующие свойства.
- Можно передавать функцию в качестве аргумента (применительно к функциям первого класса).
- Функция запоминает все переменные, которые находились в скоупе на момент её создания. Таким образом, во время вызова функции можно получить доступ к этим переменным, даже если в скоупе их уже нет.
Из примера видно, что, если изменить значение переменной outside_var
, результат останется по-прежнему равным 5. Это происходит потому, что перед тем, как изменить значение, мы определили функцию print
.
Иммутабельность состояний
Иммутабельность и Elixir — понятия неразделимые. Данное свойство позволяет Elixir избежать распространённой ситуации, при которой конкурентные процессы конфликтуют друг с другом, одновременно обращаясь к одной структуре данных. Посмотрим, как иммутабельность реализуется в Elixir.
Да, вы не ошиблись, переменным можно переприсваивать значения. Данные в Elixir по определению иммутабельны, но при этом их можно изменять. Таким образом, Elixir сочетает в себе лучшие стороны двух противоположных концепций, что превращает его в своеобразный мост для заинтересованных в ФП разработчиков, позволяя им не «нырять с головой» в новый функционал, а погружаться в него постепенно. Как минимум присваивание значений переменным осуществляется так же, как и в других языках.
В первой строке переменной num
присваивается значение 22. Затем значение num
сравнивается с числом 23. Далее переменной не присваивается новое значение, а проводится сопоставление с образцом постановкой специального символа (^) перед самой переменной. В последней строке переменная num
связывается заново и ей присваивается значение 23. В этом случае num
лишь выступает в качестве контейнера, который можно связывать с новыми данными. После чего среда выполнения избавляется от старых данных, освобождая память для новых.
Как было сказано выше, структуры данных Elixir по природе иммутабельны, поэтому беспокоиться о последствиях присваивания новых значений переменным не придётся. Давайте взглянем на код.
Попробуем заменить значения любых переменных функции на 2. Посмотрим, как будет выглядеть код.
Прежде всего компилятор пожалуется на то, что переменная string
не используется. Присваиваем переменной greeting
значение «Hello». Попробуем изменить значение переменной greeting
при помощи функции Assignment.change_me/1
, созданной ранее. Получилось 2. Но при проверке значения greeting
на экран выведется изначальное значение переменной «Hello».