Замыкания, примеси и обёртки

Замыкание — функция, возвращённая из контекста другой функции, она видит контекст внешней. Примеси (mixins) добавляют поведение в объект. Обёртки (wrappers) — функция, оборачивающая другую функцию для добавления логики (декораторы, мемоизация, throttle).

Что такое замыкание

Если функция возвращена из контекста другой функции, то она будет видеть контекст верхней функции. Это и называется замыкание. Мы замкнули функцию на контекст.

const createLog = (base) => (n) => Math.log(n) / Math.log(base);
const lg = createLog(10);
lg(1000); // base=10 живёт в замыкании

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

Чистая функция

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

  • Вход → выход
  • Никаких внешних эффектов
  • Может быть синхронной и асинхронной

Заражение нечистотой

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

Транзитивность: вызвал нечистое — стал нечистым.

Побочные эффекты могут быть быстрее

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

Цена чистоты: GC давление от множества новых объектов. Иногда mutation in-place выгоднее.

Программа vs приложение

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

ФП моделирует программу. Приложение всегда содержит state и интерфейсы для его изменения — чистый ФП-стиль для приложения сложен.

Повышать абстракцию проще

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

Натуральный лог → лог по основанию. Из общего легко породить частное через partial.

bind, call, apply

const lg = log.bind(null, 10);
// или
log.call(null, 10, 1000);
log.apply(null, [10, 1000]);

У каждой функции в JavaScript есть bind, call и apply. bind порождает новую функцию, закрепляет объектный контекст и аргументы.

rest vs spread

В одном случае троеточие — rest оператор, в другом spread. Rest забирает все аргументы в массив, spread из массива разворачивает в аргументы.

function fn(...args) { /* rest: args = [...] */ }
fn(...arr);              // spread: arr → arguments

Один синтаксис, два смысла, зависит от контекста.

compose из двух функций → reduce

const compose = (...fns) => (...args) =>
  fns.reduce((res, fn) => [fn(...res)], args)[0];

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

forEach против reduce/map/filter/every

forEach концептуально неверен. Вместо forEach всегда можно сделать reduce, filter, map, every. На каждый случай итерирования нужно использовать разный метод.

forEach остаётся, когда совсем ничего не подходит — но это намёк, что итерация имеет побочный эффект.

Не бери compose из npm

Простой compose пишется сам. Берите underscore или lodash, но не безымянный compose-пакет из npm-а, где их тысяч десять.

Примеси (mixins)

Mixins — это копирование методов из одного объекта в другой:

const Greetable = {
  greet { return `Hello, ${this.name}`; }
};
Object.assign(Person.prototype, Greetable);

Не наследование. Не «is-a», а «has-a behavior». В ФП-стиле заменяется композицией функций или decorator-обёрткой.

Обёртки (wrappers, decorators)

const memoize = (fn) => {
  const cache = new Map();
  return (...args) => {
    const key = JSON.stringify(args);
    if (cache.has(key)) return cache.get(key);
    const result = fn(...args);
    cache.set(key, result);
    return result;
  };
};

Wrapper — HOF, который возвращает функцию с дополнительным поведением. Замыкание хранит state обёртки (cache, counter, timer).

Связанные темы

Ресурсы