Pull to refresh

Функциональное программирование с точки зрения EcmaScript. Чистые функции, лямбды, имутабельность

Reading time5 min
Views15K
Привет, Хабр!

Сегодня мы начнём говорить на очень важную тему — функциональное программирование. Значение ФП в современной веб-разработке трудно переоценить. Архитектура любого крупного современного проекта включает в себя пользовательские библиотеки функций и на собеседовании любого уровня в обязательном порядке будут вопросы по ФП.

Введение в функциональное программирование


Функциональное программирование(ФП) — способ организации кода через написание набора функций.

EcmaScript, являясь мультипарадигменным языком программирования, реализует наряду с прочими и функциональную парадигму. Это означает, что функции в ES являются данными и могут быть переданы в функции, возвращены из функций и могут сами принимать функции. Т.е. функции в ES являются функциями первого класса.

Отсюда следуют следующие определения:

Функциональный агрумент(Functional argument, фунарг) — аргумент, значением которого является функция.

Функция высшего порядка(ФВП, higher-order-funtion, hof) — функция, которая принимает функции в качестве аргументов.

Функции с функциональным значением(Function valued functions) — функция, которая возвращает функцию.

Все эти типы функций условно объединяют в функции первого класса, и, как следует из определения выше, в ES все функции являются объектами первого класса.

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


Чистые функции (Pure functions, PF) — всегда возвращают предсказуемый результат.
Свойства PF:

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

Пример чистой функции:

const add = (x,y) => x+y;

Хорошим примером нечистоты функции является:

var first;
var second;

function testFn() {
  var a = 10;
  
  first = function() {
    return ++a;
  }

  second = function() {
   return --a;
  }

  a = 2;
  first();//3
}

testFn();

first();//4
second();//3

Представьте сколь усложняется написание тестов для этого примера и сколь оно упрощается для чистых функций!

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

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

Думаю, вы заметили, что в примерах на чистые функции я перешёл на синтаксис ES6. Это было сделано сознательно. Данный синтаксис функций получил название «стрелочные функции»(arrow functions), но на самом деле это реализация математической абстракции, придуманной давным давно. Об этом далее.

Лямбда — функции


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

Термин Лямбда-исчисления ввёл ещё в 1930-х годах Алонзо Черч. По сути лямбда-исчисления не более чем формальная форма описания математического уравнения. Более подробно тут.

В ES на лямбда-функция очень часто реализуют замыкание:

const add = x => y => x + y;

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

Сравните с этим кодом:

funtion add(x) {
  return function (y) {
   return x + y;
  }
}

Очевидно, первый вариант выглядит лучше.

Имутабельность


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

Неизменяемость — золотой грааль функционального программирования.

Рассмотрим пример:

const impureAddProp = (key, value, object) => {
  object[key] = value;//Добавляем свойство объекту
};
const User= {
  name: 'Alex'
};
impureAddProp ('isAdmin', true, User);

Как видите, в данном примере мы мутировали объект User, добавив ему свойство. Теперь объект User это некое «разделяемое состояние» для функции impureAddProp и других функций, которые будут его мутировать. Данный подход труднее тестировать, т.к. меняя любую функцию, взаимодействующую с разделяемым состоянием, всегда нужно иметь ввиду возможные ошибки в других функциях.

С точки зрения функционального программирования правильно было бы так:

const pureAddProp = (key, value, object) => ({
  ...object,
  [key]: value
});
const User= {
  name: 'Alex'
};
const Admin= pureAddProp ('isAdmin', true, User);

Так объект User останется неизменным. Мы изменяем копию данных, а это всегда безопасно.

Заключение


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

Функциональное программирование реализуют многие библиотеки. Это и rambda, и lodash, и многие другие. В реальном проекте вы, разумеется, будете использовать именно их. Под капотом же любых библиотек будет всё тот же нативный javascript, поэтому в следующих статьях мы будем разбирать ФП, реализуя все его концепции именно на нативном JS.

Постскриптум


Начиная писать статьи, я имел ввиду следующий план:

  • писать переводы интересных англоязычных статей
  • осветить несколько актуальных направлений в JS (ключевые концепции, ООП с точки зрения спецификации EcmaScript, паттерны, функциональное программирование).

На сегодняшний день уже написаны головные статьи трёх направлений:

  1. this и ScopeChain в EcmaScript — тут я описал такие ключевые концепции спецификации как контекст исполнения, ключевое слово this и свойство контекста ScopeChain(цепочка областей видимости). В рамках этого направления буквально сегодня вышла моя статья о Лексическом окружении и Замыкании.
  2. Взгляд со стороны EcmaScript на общую теорию ООП — тут была описана разница между статической классовой типизацией и динамической прототипной организацией, разобраны делегирующая модель и утиная типизация
  3. Элегантные паттерны в современном JavaScript (сборная статья по циклу от Bill Sourour) — тут разобраны два паттерна, которые могут пригодиться в каких-то ситуациях. Мой подход в плане паттернов довольно прост: лучше знать как можно больше паттернов, т.к. рано или поздно пригодятся

И вот настала очередь функционального программирования. В дальнейшем я буду писать статьи в продолжении каждого из этих направлений. Например, следующая статья будет о ключевых понятиях ООП: инкапсуляции, абстракции, примесях(и штрихах), интерфейсах и т.д… Также я планирую рассказать о том, как ООП в ES реализовано под капотом, т.е. о свойствах [[Prototype]], [[Class]] и многом другом. Рассказать о том, как v8 создаёт сущности и инстанции классов, функции.

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

В статьях я либо обозреваю концепции, рассказываю как они устроены под капотом( на мой взгляд это улучшает понимание того, что мы пишем и почему пишем именно так), либо рассказываю про какие-то вещи, расширяющие кругозор. На мой взгляд это очень важно. Взгляните на такие компании как Яндекс или Едадил, они постоянно рассказывают о каких-то оригинальных своих идеях. То это битовые карты в реакте, то vue приложение практически полностью на es6 классах. Большинству веб-разработчиков такие вещи просто бы не пришли в голову. Для этого и нужен широкий кругозор.

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

До будущих статей, друзья!
Tags:
Hubs:
Total votes 19: ↑13 and ↓6+7
Comments35

Articles