Декларативные языки на примерах с JavaScript

Posted by

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

В статьях, посвященных декларативному программированию, зачастую упоминаются SQL, HTML в качестве примеров декларативных языков. В рамках этих «языков» мы действительно формулируем желаемый результат, вместо инструкции по его достижению, что вполне соответствует рассматриваемому концепту. Однако я хочу выделить общую черту этих доменных языков:

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

Писать код на JavaScript с таким тезисом звучит как вызов и в какой-то степени абсурдно, не правда ли?

Вспомните, как часто вам приходилось писать подобный код в JSX, чтобы показать или скрыть компонент:

Давайте перепишем этот кусочек кода в более декларативной манере:

Как было сказано, это более декларативный вариант кода. В конечном итоге мы все равно упремся на логическую конструкцию `if`, но мы намерены прятать его все дальше от основного кода.

Наличие свойства `visible` будет подталкивать нас к написанию декларативного кода несмотря на то, что сам компонент частично реализован в императивном стиле.

Попробуем разобрать логический оператор `switch`, который является не только ближайшим родственником `if`, но и любимчиком многих разработчиков:

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

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

Основываясь на вышеупомянутую идею, перепишем JSX-код в виде наборов функций. Стоит учесть, что `switch/case` — это зарезервированные слова JavaScript, поэтому мы добавим к ним нижние подчеркивания:

Как вы можете заметить, декларативный код не обязательно означает HTML, JSX или SQL. В JavaScript мы можем представить синтаксис этих языков с помощью функций. Если нас учили, что функции должны представлять собой действия и их названия должны начинаться с глагола, например: `find`, `setTitle`, то в данном случае наши функции не всегда будут выражать действие и могут обозначать сущность. К примеру, SQL-запрос можно записать в следующем виде:

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

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

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

Предлагаю еще раз взглянуть на конструкции `switch/case` и представить их в виде обычной функции:

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

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

Для ясности, предлагаю развернуть функцию `selectComponent`, после вызова функций `when` мы получим следующий код:

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

Если мы вернемся к первой версии функции `selectComponent`, станет ясно, что расширить её функционал без внесения изменений не получится. Используя данный подход, мы способствуем разбиению кода на мелкие функции, каждая из которых решает конкретную задачу, и в дальнейшем они менее подвержены изменениям.

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

В следующей статье мы подробно рассмотрим данную технику на примере создания редьюсеров Redux. Узнаем, как заменить `try/catch`  и продолжим обсуждение функционального программирования.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *