Декларативный JavaScript
На примере JavaScript объяснили, как пересекается декларативное программирование с функциональным, и как кодить на JS декларативно.
1К открытий8К показов
Эта статья ориентирована на читателей, которые уже знакомы с функциональным программированием, но, возможно, не до конца разобрались с применением некоторых ключевых идей на практике. Мы постараемся разобрать их на конкретных примерах и понять, как пересекается декларативное программирование с функциональным. Следует отметить, что в контексте 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`
и продолжим обсуждение функционального программирования.
1К открытий8К показов