Функциональное программирование проникло в большую часть основного мира программирования - большую часть экосистемы JS, Linq для C #, даже функции высшего порядка в Java. Это Java в 2018 году:

getUserName(users, user -> user.getUserName());

FP настолько полезен и удобен, что он нашел свое место во всех современных языках программирования в массовом использовании, о которых я могу думать.

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

FP лежит в основе обеих доминирующих фреймворков, React (отказ от разделяемой изменяемой DOM является мотивацией для его архитектуры и одностороннего потока данных) и Angular (RxJS - это библиотека служебных операторов, которые действуют с потоками посредством более высоких уровней). функции заказа - они широко используются в Angular). Redux и ngrx / store тоже: функциональны по обоим параметрам.

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

Для менеджеров, незнакомых с FP или его распространенностью в современной экосистеме кода, это может показаться разумным предложением. В конце концов, разве ООП не служило миру программного обеспечения в течение 30 лет? Почему бы и дальше этого не делать?

Давайте на минутку рассмотрим эту идею.

Что вообще значило бы «запретить» FP как политику?

Что такое функциональное программирование?

Мое любимое определение функционального программирования:

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

Чистая функция - это функция, которая:

  • Учитывая один и тот же ввод, всегда возвращать один и тот же вывод
  • Не имеет побочных эффектов

Суть FP действительно сводится к следующему:

  • Программа с функциями
  • Избегайте общего изменяемого состояния и побочных эффектов

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

Между прочим, согласно Алану Кею, зачинщику всего современного ООП, суть ООП заключается в следующем:

  • Инкапсуляция
  • Передача сообщений

Таким образом, ООП - это просто еще один подход к предотвращению общего изменяемого состояния и побочных эффектов.

Ясно, что противоположность FP - это не ООП. Противоположностью FP является неструктурированное процедурное программирование.

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

То же самое и с JavaScript. Когда Брендана Эйха наняли для создания JavaScript, у него были следующие идеи:

  • Схема в браузере (FP)
  • Похож на Java (ООП)

В JavaScript вы можете попытаться отдать предпочтение одному или другому, но, к лучшему или худшему, они неразрывно связаны. Инкапсуляция в JavaScript основана на замыканиях - концепции функционального программирования.

Скорее всего, вы уже занимаетесь функциональным программированием, знаете вы об этом или нет.

Как НЕ делать FP:

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

const getName = obj => obj.name;
const name = getName({ uid: '123', name: 'Banksy' }); // Banksy

Давайте проведем рефакторинг, чтобы это больше не FP. Вместо этого мы могли бы сделать его классом с общедоступным свойством. Поскольку в нем не используется инкапсуляция, называть это ООП - непросто. Может быть, нам следует назвать это «процедурным объектным программированием» ?:

class User {
  constructor ({name}) {
    this.name = name;
  }
  getName () {
    return this.name;
  }
}
const myUser = new User({ uid: '123', name: 'Banksy' });
const name = myUser.getName(); // Banksy

Поздравляю! Мы только что превратили 2 строки кода в 11 и представили вероятность неконтролируемой внешней мутации. Что мы приобрели?

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

Предыдущая функция getName() работала с любым входным объектом. Этот тоже (потому что это JavaScript, и мы можем делегировать методы случайным объектам), но это намного более неудобно. Мы могли бы позволить двум классам наследовать от общего базового класса, но это подразумевает отношения, которые могут вообще не существовать между классами объектов.

Забудьте о возможности повторного использования. Мы только что смыли его в канализацию. Вот как выглядит дублирование кода:

class Friend {
  constructor ({name}) {
    this.name = name;
  }
  getName () {
    return this.name;
  }
}

Отзывчивый студент вмешивается из глубины комнаты:

«Конечно же, создайте персональный класс!»

Но потом:

class Country {
  constructor ({name}) {
    this.name = name;
  }
  getName () {
    return this.name;
  }
}

«Но очевидно, что это разные типы. Конечно, вы не можете использовать метод человека для страны! "

На что я отвечаю: «Почему бы и нет?»

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

Примечание. Для Java-разработчиков речь идет не о статических типах. Некоторые языки FP имеют отличные системы статических типов и по-прежнему разделяют это преимущество, используя такие функции, как структурные типы и / или типы более высокого порядка.

Этот пример тривиален, но экономия объема кода, которую мы получаем с помощью этого трюка, огромна.

Это позволяет библиотекам, таким как autodux, автоматически генерировать логику предметной области для любого объекта, состоящего из пар геттер / сеттер (и многого другого, кроме того). Один только этот трюк может сократить код логики вашего домена вдвое или даже лучше.

Больше никаких функций высшего порядка

Поскольку большинство (но не все) функций высшего порядка используют преимущества чистых функций для возврата тех же значений на основе одних и тех же входных данных, вы также не можете использовать такие функции, как .map(), .filter(), reduce(), не добавляя побочного эффекта, просто сказать, что ты не чист:

const arr = [1,2,3];
const double = n => n * 2;
const doubledArr = arr.map(double);

Становится:

const arr = [1,2,3];
const double = (n, i) => {
  console.log('Random side-effect for no reason.');
  console.log(`Oh, I know, we could directly save the output
to the database and tightly couple our domain logic to our I/O. That'll be fine. Nobody else will need to multiply by 2, right?`);
  saveToDB(i, n);
  return n * 2;
};
const doubledArr = arr.map(double);

RIP, Функциональная композиция 1958–2018 гг.

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

const wrapEveryPage = compose(
  withRedux,
  withEnv,
  withLoader,
  withTheme,
  withLayout,
  withFeatures({ initialFeatures })
);

Вам придется вручную импортировать каждый из них в каждый компонент или, что еще хуже, спуститься в запутанную, негибкую иерархию наследования классов (справедливо считающуюся антипаттерном большинством здравомыслящих людей, даже [особенно?] От ОО дизайн-канон ).

Прощай, обещания и async / await

Обещания - это монады. Технически из теории категорий, но я слышал, что они тоже относятся к FP, потому что они есть в Haskell, и он использует монады, чтобы сделать все чистым и ленивым.

Честно говоря, потеря руководств по монадам и функторам - это, вероятно, хорошо. Эти вещи намного проще, чем мы говорим! Я не зря учу людей использовать Array.prototype.map и обещания до, я знакомлю с общими концепциями функторов и монад.

Вы знаете, как ими пользоваться? Вы прошли более половины пути к пониманию функторов и монад.

Итак, чтобы избежать ФП

  • Не используйте ни один из самых популярных фреймворков или библиотек JavaScript (все они предадут вас FP!).
  • Не пишите чистые функции.
  • Не используйте множество встроенных функций JavaScript: большинство математических функций (потому что они чистые), немутантные методы строк и массивов, .map (), .filter (), .forEach (), promises, или async / await.
  • Пишите ненужные классы.
  • Удвойте (или более) свою логику домена, вручную написав геттеры и сеттеры буквально для всего.
  • Возьмите «читаемый, явный» императивный подход и запутайте логику своей предметной области с проблемами рендеринга и сетевого ввода-вывода.

И попрощаться с:

  • Отладка путешествия во времени
  • Легкая разработка функции отмены / повтора
  • Надежные, последовательные модульные тесты
  • Mock и D / I бесплатное тестирование
  • Быстрые модульные тесты без зависимости от сетевого ввода-вывода
  • Небольшие, тестируемые, отлаживаемые, поддерживаемые кодовые базы

Избегать функционального программирования как политики? Без проблем.

Ой, подожди.

Узнайте больше на EricElliottJS.com

Видеоуроки по функциональному программированию доступны для членов EricElliottJS.com. Если вы не являетесь участником, зарегистрируйтесь сегодня.

Эрик Эллиотт - автор книги Программирование приложений JavaScript (O’Reilly) и соучредитель платформы для наставничества по программному обеспечению DevAnywhere.io. Он участвовал в разработке программного обеспечения для Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC и ведущие музыканты, включая Usher, Frank Ocean, Metallica и многие другие.

Он работает удаленно с самой красивой женщиной в мире.