Функции высшего порядка: map, filter и reduce на примерах
Основы4 минРедакция Innovialabs
Коротко
Функция высшего порядка принимает или возвращает другую функцию; map преобразует каждый элемент, filter оставляет прошедшие проверку, а reduce сворачивает коллекцию в одно значение.
Короткий ответ
Функция высшего порядка — это функция, которая принимает другую функцию как аргумент или возвращает её. Три самые частые работают с коллекциями:
map— применяет функцию к каждому элементу и возвращает новую коллекцию той же длины.filter— оставляет только элементы, для которых функция вернула true.reduce— объединяет все элементы в одно значение: сумму, объект, максимум.
Они заменяют типичные циклы коротким описанием того, что нужно получить, а не как перебирать.
Функции — это значения
В JavaScript и Python функция — обычное значение. Её можно сохранить в переменную, передать в другую функцию и вернуть.
const double = (x) => x * 2;
const apply = (fn, value) => fn(value);
apply(double, 5); // 10
def double(x):
return x * 2
def apply(fn, value):
return fn(value)
apply(double, 5) # 10
map, filter и reduce — именно такие функции: цикл встроен внутрь, а вы передаёте только шаг.
От цикла к map, filter, reduce
Задача: из списка заказов взять оплаченные и посчитать общую сумму.
Циклом:
let total = 0;
for (const order of orders) {
if (order.paid) {
total += order.amount;
}
}
Функциями высшего порядка:
const total = orders
.filter((o) => o.paid)
.map((o) => o.amount)
.reduce((sum, a) => sum + a, 0);
То же на Python:
from functools import reduce
paid = filter(lambda o: o["paid"], orders)
amounts = map(lambda o: o["amount"], paid)
total = reduce(lambda s, a: s + a, amounts, 0)
В идиоматичном Python обычно предпочитают comprehensions и встроенные функции:
total = sum(o["amount"] for o in orders if o["paid"])
Учтите: в Python 3 map и filter возвращают ленивые итераторы, а не списки. Оберните результат в list(), если он нужен повторно.
Как работает reduce
reduce принимает функцию с двумя аргументами — аккумулятором и текущим элементом — и начальное значение.
const counts = ["a", "b", "a"].reduce((acc, letter) => {
acc[letter] = (acc[letter] ?? 0) + 1;
return acc;
}, {});
// { a: 2, b: 1 }
Всегда передавайте начальное значение. Без него JavaScript берёт первый элемент как стартовый и бросает TypeError на пустом массиве.
Цепочки
Цепочка читается сверху вниз, как рецепт: отфильтровать, преобразовать, агрегировать. Несколько практических правил:
- Ставьте filter перед map, когда это возможно, — преобразуете меньше элементов.
- Держите колбэки чистыми: без побочных эффектов и изменения внешних переменных. Тогда каждый шаг легко тестировать отдельно.
- Давайте сложным колбэкам имена:
.filter(isActiveUser)читается лучше трёхстрочной стрелочной функции. - Помните, что каждый шаг цепочки массива в JavaScript создаёт новый промежуточный массив. Для обычных данных интерфейса это нормально; для очень больших объёмов в горячем коде — измеряйте.
Пределы читаемости
Функции высшего порядка не делают код читаемым автоматически. Тревожные признаки:
- Колбэк
reduceдлиннее нескольких строк, с ветвлениями и вложенными объектами. - Цепочки из пяти и более шагов, где без комментариев непонятно, какая форма у данных между шагами.
mapтолько ради побочных эффектов (логирование, отправка запросов) — для этого естьforEachили цикл.
Когда обычный цикл лучше
| Ситуация | Лучший выбор |
|---|---|
Нужно остановиться раньше (break) | цикл или find / some / every |
Последовательные вызовы await один за другим | for...of с await |
| Сложное состояние с несколькими накопителями | цикл |
| Критичный к скорости код на огромных массивах | цикл, после замеров |
| Простое преобразование, фильтр или сумма | map / filter / reduce |
forEach, map и reduce не ждут асинхронные колбэки по очереди. map с async-функцией возвращает массив промисов — его можно передать в Promise.all, если нужно именно параллельное выполнение.
Частые ошибки
- Забыть
returnв колбэке с фигурными скобками — получатся значенияundefined. - Изменять исходный массив внутри
map. - Не передать начальное значение в
reduce. - Передать функцию с лишними параметрами напрямую:
["1", "2", "3"].map(parseInt)даёт[1, NaN, NaN], потому чтоmapпередаёт ещё и индекс, который становится основанием системы счисления.