Замыкания, примеси и обёртки
Замыкание — функция, возвращённая из контекста другой функции, она видит контекст внешней. Примеси (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).
Связанные темы
Ресурсы
- Летняя школа 2017: 8_aKQBhzpl8