Функциональные методы: map, filter, reduce

map, filter и reduce превращают многословные циклы в одну выразительную строку. Это сердце декларативного мышления, на котором стоит SwiftUI.
Суть урока: вместо того чтобы описывать КАК перебирать коллекцию, вы описываете ЧТО хотите получить. map преобразует каждый элемент, filter отбирает нужные, reduce сворачивает всё в одно значение.

Эти методы принимают замыкание — небольшую функцию без имени, описывающую правило преобразования. Начнём с map: он берёт каждый элемент и возвращает новый, формируя новый массив той же длины.

let prices = [100, 250, 80]
let withTax = prices.map { price in price * 2 }
// [200, 500, 160]

let labels = prices.map { "\($0) руб." }   // $0 — первый аргумент
// ["100 руб.", "250 руб.", "80 руб."]

Запись $0 — сокращение для первого аргумента замыкания. filter отбирает элементы, для которых замыкание вернуло true:

let numbers = [3, 8, 1, 12, 5]
let big = numbers.filter { $0 > 4 }
// [8, 12, 5]

reduce сворачивает коллекцию в одно значение, начиная с заданного: первый аргумент — стартовое значение, второй — правило объединения накопителя и текущего элемента:

let total = numbers.reduce(0) { acc, item in acc + item }
// 29
let sum = numbers.reduce(0, +)   // ещё короче: 29

Методы можно соединять в цепочку, читая её сверху вниз как конвейер данных:

let result = prices
    .filter { $0 >= 100 }     // оставить дорогие
    .map { $0 * 2 }           // удвоить
    .reduce(0, +)             // сложить
// (100*2) + (250*2) = 700
[100, 250, 80]
     | filter { $0 >= 100 }
     v
[100, 250]
     | map { $0 * 2 }
     v
[200, 500]
     | reduce(0, +)
     v
   700

Попробуй сам ▶ — запусти код прямо в браузере (Pyodide). Здесь нет Swift, но логика та же, что под капотом мобильного кода:

# map / filter / reduce есть и в Python — мышление абсолютно то же.
from functools import reduce

prices = [100, 250, 80]

with_tax = list(map(lambda p: p * 2, prices))
print('map:', with_tax)

big = list(filter(lambda p: p >= 100, prices))
print('filter:', big)

total = reduce(lambda acc, x: acc + x, big, 0)
print('reduce:', total)

# Конвейер целиком
pipeline = reduce(lambda a, x: a + x,
                  map(lambda p: p * 2,
                      filter(lambda p: p >= 100, prices)), 0)
print('pipeline:', pipeline)

Как работает под капотом

Замыкание — это самостоятельный блок кода, который можно передавать как значение. map, filter и reduce — обычные методы протокола Sequence, принимающие такое замыкание. Компилятор часто встраивает (inline) короткие замыкания, поэтому функциональный стиль обычно не медленнее ручного цикла. Эта же идея «передать поведение как данные» лежит в основе SwiftUI, где вы описываете интерфейс как функцию от состояния.

Частые ошибки

  • Путать map и forEach. map возвращает новый массив; forEach просто выполняет действие и ничего не возвращает.
  • Слишком длинные цепочки. Пять-шесть звеньев тяжело читать — иногда понятнее обычный цикл или промежуточная переменная.
  • Забыть стартовое значение reduce. Первый аргумент обязателен и задаёт тип результата.

Best practices

  • Используйте сокращения $0, $1 для коротких замыканий, но именуйте аргументы в сложных.
  • Стройте читаемые конвейеры: filter перед map, чтобы преобразовывать меньше элементов.
  • compactMap пригодится, чтобы преобразовать и сразу отбросить nil-результаты.

Итоги. map, filter и reduce — декларативный способ обработки данных. Они короче и выразительнее циклов, а главное — приучают мыслить в стиле «что я хочу получить», который вы будете применять во всём SwiftUI.

Шире контекста

Декларативное мышление, которое вы тренируете на map, filter и reduce, — это та же ментальная модель, что лежит в основе всего SwiftUI. Там вы описываете интерфейс как функцию от данных, здесь — преобразование данных как цепочку чистых операций. В обоих случаях вы говорите «что я хочу получить», а не «какими шагами это собрать». Кроме трёх базовых методов, в арсенале Swift есть compactMap (преобразовать и отбросить nil), flatMap (развернуть вложенные коллекции), sorted, prefix, contains и десятки других — все они складываются в выразительные конвейеры. Важная деталь производительности: ставьте filter раньше map, чтобы преобразовывать только нужные элементы. Овладев этим стилем, вы будете писать обработку данных, которая читается почти как описание задачи на естественном языке, и при этом остаётся быстрой благодаря встраиванию замыканий компилятором.

Проверьте себя
1. Что возвращает метод map?
AОдно свёрнутое значение
BНовый массив с преобразованными элементами
CОтфильтрованное подмножество
DНичего (Void)
2. Какой метод сворачивает коллекцию в одно итоговое значение?
Amap
Bfilter
Creduce
DforEach