Большая часть того, что я буду обсуждать в этой статье, - это знания, накопленные при чтении Функционального программирования в JavaScript », написанного Луисом Атенсио. Давайте копать прямо ...

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

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

Абстрактные потоки управления

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

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

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for(let i = 0; i < array.length; i++) {
    array[i]= Math.pow(array[i], 2);
}
array;
//-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

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

В декларативном программировании описание программы отделяется от оценки. Он фокусируется на использовании выражений для описания логики программы без обязательного указания на ее поток управления или изменения состояния.

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

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(num => Math.pow(num, 2));
//-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

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

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

Избегайте побочных эффектов

Функциональное программирование основано на предпосылке, что вы используете чистые функции (функции без побочных эффектов) в качестве строительных блоков при построении ваших программ. Чистые функции имеют следующие характеристики:

  • Они зависят только от предоставленного входа, а не от какого-либо скрытого или внешнего входа.
  • Они не вносят изменений, выходящих за рамки их возможностей, таких как изменение глобального объекта.

Интуитивно понятно, что любая функция, не отвечающая этим требованиям, считается «нечистой». Рассмотрим следующую функцию:

var counter = 0;
function increment() {
    return ++counter;
}

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

Кто-то может спросить: «Если вы не можете создавать и изменять объекты или печатать на консоли, какую практическую ценность вы получите от такой программы?». Действительно, чистые функции могут быть трудными для использования в мире, полном динамических поведение и мутации. Но практическое функциональное программирование не ограничивает все изменения состояния; он просто предоставляет основу, которая поможет вам управлять ими и уменьшить их, одновременно позволяя отделить чистое от нечистого. Нечистый код вызывает внешне видимые побочные эффекты.

Уменьшить мутацию состояния

Неизменяемые данные - это данные, которые нельзя изменить после создания. В JavaScript, как и во многих других языках, все примитивные типы (String, Number и т. Д.) По своей сути неизменны. Но другие объекты, например массивы, не являются неизменяемыми.

Состояние программы можно определить как моментальный снимок данных, хранящихся во всех ее объектах в любой момент времени. К сожалению, JavaScript - один из худших языков, когда речь идет о защите состояния объекта. Объект JavaScript очень динамичен, и вы можете изменять, добавлять или удалять его свойства в любой момент. Хотя это может дать вам свободу делать много приятных вещей, это также может привести к созданию кода, который чрезвычайно сложно поддерживать.

Если немного отступить, вы можете спросить, почему рекомендуется всегда удалять ручные циклы из кода? Ну, ручной цикл - это императивная структура управления, которую трудно использовать повторно и сложно подключить к другим операциям. Кроме того, это подразумевает код, который постоянно меняется или видоизменяется в ответ на новые итерации.

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

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

ES6 использует ключевое слово const для создания постоянных ссылок. Это перемещает стрелку в правильном направлении, потому что константы нельзя переназначить или повторно объявить. В практическом функциональном программировании вы можете использовать const как средство для переноса простых данных конфигурации (строк URL, имен баз данных и т. Д.) В вашу функциональную программу. Но это не решает проблемы изменчивости до уровня, необходимого для FP. Вы можете запретить переназначение переменной, но как предотвратить изменение внутреннего состояния объекта? Например, этот код будет вполне приемлемым:

const student = new Student('Alonzo', 'Church', '666-66-6666', 'Princeton');
student.lastname = 'Mourning';

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

function zipCode(code, location) {
  let _code = code;
  let _location = location || '';
  return {
    code: function () {
      return _code;
    },
    location: function () {
      return _location;
    },
    fromString: function (str) {
      let parts = str.split('-');
      return zipCode(parts[0], parts[1]);
    },
    toString: function () {
      return _code + '-' + _location;
    }
  };
}

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

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

В заключении…

Размышляя о дизайне своего приложения, задайте себе следующие вопросы:
* Постоянно ли я меняю свой код для поддержки дополнительных функций?
* Если я изменю один файл или функцию, это повлияет на другой?
* Много ли в моем коде повторений?
* Мой код неструктурирован или его трудно понять?

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

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

Функция - это любое вызываемое выражение, которое можно вычислить, применив к нему оператор (). Функции могут возвращать вызывающему объекту либо вычисленное значение, либо неопределенное (функция void). Поскольку FP работает во многом как математика, функции имеют смысл только тогда, когда они дают полезный результат (не null или undefined), в противном случае предполагается, что они изменяют внешние данные и вызывают побочные эффекты.

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

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