Что делает метод compactMap в Swift
Что проверяют
Понимаете ли вы, как Swift работает с опционалами в функциональном стиле, и различаете ли map, flatMap и compactMap. Следом часто дают массив [Int??] или спрашивают, куда делся flatMap для опционалов.
Как отвечать
compactMap это метод Sequence, который применяет замыкание к каждому элементу и собирает в массив только результаты, не равные nil. Замыкание возвращает T?, метод возвращает [T]. Сложность линейная, порядок элементов сохраняется.
- преобразование с отбрасыванием:
["1", "2", "x"].compactMap(Int.init)даёт[1, 2], строка, которую не удалось распарсить, просто пропадает; - снятие опциональности:
[Int?]черезcompactMap { $0 }превращается в[Int]; - фильтрация по типу:
view.subviews.compactMap { $0 as? UILabel }возвращает только лейблы, уже приведённые к нужному типу; - сравнение с
map:mapс тем же замыканием вернёт[Int?]и сохранитnilна своих местах; - сравнение с
flatMap: до Swift 4.1 эту работу делал перегруженныйflatMap, его переименовали, потому что путались. СейчасflatMapна последовательностях сплющивает вложенные коллекции:[[1, 2], [3]]становится[1, 2, 3].
Есть родственники: compactMapValues у Dictionary (Swift 5) отбрасывает пары с nil в значении, lazy.compactMap не создаёт промежуточный массив, а в Combine и у AsyncSequence есть одноимённый оператор, который пропускает nil в потоке событий.
Где спотыкаются
Думают, что compactMap убирает nil из исходного массива. Он смотрит на результат замыкания: если замыкание возвращает не опционал, отбрасывать нечего, и метод работает как map.
Путаются с двойной опциональностью. У [Int??] вызов compactMap { $0 } снимает только внешний слой, в результате [Int?], и внутренние nil остаются.
Используют compactMap там, где ошибка должна быть заметна. При разборе ответа сервера он молча выкинет битые элементы, и пользователь увидит пять товаров из шести без единой записи в логе. Если пропуск означает баг, лучше map с throws или хотя бы логирование внутри замыкания.