Примеры применения Ramda в JavaScript | Блог Work Solutions
Мыслим в парадигме функционального программирования с Ramda
Разработчикам10 ноября 2022

Мыслим в парадигме функционального программирования с Ramda

Евгений ЖиленкоФронтенд-разработчик

Статья будет полезна тем, кто хочет приоткрыть для себя дверь в мир функционального программирования. В начале рассмотрим отличия ООП и ФП, затем как сочетать функции друг с другом и в конце применим знаменитые фишки функционального подхода.  

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

Отличия объектно ориентированного и функционального подходов

Что такое ООП?

  1. Работаем с объектами, то есть классами и экземплярами этих классов;
  2. Пишем в императивном стиле;
  3. Классы тесно связаны между собой;
  4. Данные и методы по работе с классом лежат в самом классе. Эти данные должны быть инкапсулированы и доступ к ним должен осуществляться через геттеры/сеттеры и методы этого класса;
  5. Данные являются изменяемыми. Мы можем их изменить в любой момент работы нашего кода.
     

Что такое ФП?

  1. Работаем с функциями;
  2. Пишем в декларативном стиле;
  3. Каждая функция - самостоятельная единица;
  4. Используем чистые функции. Они не производят побочных эффектов, то есть не изменяют входные параметры,ничего не выводят, ничего не присваивают внешним переменным, не вызывают нечистые функции, не обращаются к внешним API, работают детерминировано; 
  5. Данные являются иммутабельными.


Основное отличие в том, что в ФП важно не то, как мы хотим что-то получить, а что мы хотим получить.

С чего начать, чтобы мыслить в парадигме функционального программирования?

Сперва попробуем вместо циклов использовать итерационные функции. Применим функции, которые дает нам Ramda :

 Для операций с массивами, в Ramda имеются и другие функции:

Начинаем соединять функции

В предыдущих примерах мы применяли функцию find для нахождения первого нечетного числа в массиве. Если бы у нас была задача найти первое четное число, мы создали бы функцию isEven и использовали ее. Но мы знаем, что любое четное число не является нечетным. Здесь мы переиспользуем нашу старую функцию isOdd. В Ramda есть функция complement, которая является функцией высшего порядка. Она принимает в себя функцию, в нашем примере isOdd. На выходе получится новая функция, возвращающая true, когда наша оригинальная функция isOdd возвращает false, и наоборот:

Она делает тоже самое, что оператор !. Давайте запишем эту функции в переменную с соответствующим именем, чтобы мы могли переиспользовать её:

Существуют аналоги операторов && и ||  — функции botheither соответственно:
В Ramda также присутствуют функции allPass и anyPass : 

Допустим, нужно обработать данные друг за другом, конвейером. Например, перемножить два числа, к произведению прибавить единицу, а результат возвести в квадрат:

Заметьте, что каждая последующая функция применяется к результату предыдущей функции в конвейере. Ramda предоставляет инструмент pipe. Эта функция принимает список из нескольких функций, затем возвращает новую, которая ожидает на вход такое же количество параметров, что и первая функция в списке, передает результат работы первой функции во вторую, и так далее. Результат работы заключительной функции в списке будет являться  результатом прохождения всего конвейера. Есть ограничение, каждая функция после первой должна ожидать только один аргумент.
Все функции в Ramda автоматически каррированы. Это означает, что вы можете вызывать функцию с меньшим количеством параметров, чем она требует, и она вернет «частично примененную» функцию. Функции будут продолжать возвращать новые функции пока все параметры не будут переданы. И когда будет передан последний параметр произойдет вызов последней функции, который вернет финальный результат.

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


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

Специализация происходит от возможности каррированых функций быть «частично примененными». Мы можем сохранить эти частично примененные функции в переменные и использовать позже.

Другая важная концепция — это композиция функций. Мы можем комбинировать функции, чтобы получить новую функцию. Тут мы создали новую функцию, которая возвращает результат работы обеих функций double и increment. Мы сделали композицию функций. Выглядит неплохо, но что будет если мы захотим сделать композицию из более чем двух функций?
Теперь это выглядит плохо. Но мы уже знаем функцию pipe и с ее помощью можем записать в более человеко-читаемой манере.
Это же можно применить и к каррированым функциям. Возьмем для примера встроенные функции Ramda, хотя это будет справедливо для любой каррированой функции.

В данном примере обе функции multiply и add принимают 2 параметра, но мы передали только один. Каждая из них вернула новую функцию, которая ждет последнего параметра. Таким образом, когда мы вызываем mathPipe со значением 10, первая, частично примененная, функция возвращает нам 40 и затем это значение передается во вторую, частично примененную, функцию и в итоге мы получим 42. 


 На простых примерах мы рассмотрели, как функциональное программирование помогает писать  более чистый код, который можно переиспользовать.   

Другие статьи

Ко всем статьям

Интересные статьи и кейсы
от Work Solutions

Нажимая кнопку «Подписаться», я даю согласие на обработку персональных данных

Спасибо за подписку!