Привет, Хабр!
Меня зовут Александр Зимин, я iOS-разработчик в Badoo. Это перевод статьи моего коллеги Швиба, в которой он рассказал, что из себя представляла функция flatMap в Swift и почему одну из её перегрузок переименовали в compactMap. Статья полезна как для понимания процессов, происходящих в репозитории Swift и его эволюции, так и для общего развития.
В функциональном программировании есть чёткое определение того, что должна представлять собой функция flatMap
. Метод flatMap
берёт список и преобразующую функцию (которая для каждого преобразования ожидает получить ноль или больше значений), применяет её к каждому элементу списка и создаёт единый (flattened) список. Такое поведение отличается от простой функции map
, которая применяет преобразование к каждому значению и для каждого преобразования ожидает получить только одно значение.
Уже на протяжении нескольких версий в Swift есть map
и flatMap
. Однако в Swift 4.1 вы больше не можете применять flatMap
к последовательности значений и при этом передавать замыкание, которое возвращает опциональное значение. Для этого теперь есть метод compactMap
.
Поначалу может быть не так просто понять суть нововведения. Если flatMap
хорошо работал, зачем вводить отдельный метод? Давайте разберёмся.
Стандартная библиотека Swift до версии 4.1 предоставляла три реализации перегрузки (overloads) для flatMap
:
1. Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element], где S : Sequence
2. Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
3. Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
flatMap
сводит все эти преобразованные последовательности в финальную последовательность, возвращаемую в качестве результата. Например:let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
let flattened = array.flatMap { $0 } // [1, 2, 3, 4, 5, 6, 7, 8, 9]
flatMap
. Мы преобразуем (map) каждый элемент исходного списка и создаём новую последовательность. Благодаря flatMap
конечный результат представляет собой сплющенную структуру из преобразованных последовательностей.let a: Int? = 2
let transformedA = a.flatMap { $0 * 2 } // 4
let b: Int? = nil
let transformedB = b.flatMap { $0 * 2 } // nil
compactMap
. Эта версия выглядит так же, как и первая, но есть важное отличие. В данном случае замыкание возвращает optional. flatMap
обрабатывает его, пропуская возвращаемые nil-значения, а все остальные — включает в результат в виде значений без обёртки.let array = [1, 2, 3, 4, nil, 5, 6, nil, 7]
let arrayWithoutNils = array.flatMap { $0 } // [1, 2, 3, 4, 5, 6, 7]
flatMap
ближе к map
, чем чисто функциональное определение flatMap
. И проблема с этой перегрузкой заключается в том, что вы можете неправильно использовать её там, где отлично работала бы map
.let array = [1, 2, 3, 4, 5, 6]
let transformed = array.flatMap { $0 } // same as array.map { $0 }
flatMap
соответствует третьей перегрузке, неявно обёртывая преобразованное значение в optional, а затем убирая обёртку для добавления в результат. Ситуация становится особенно интересной, если неправильно использовать преобразование строковых значений.struct Person {
let name: String
}
let people = [Person(name: “Foo”), Person(name: “Bar”)]
let names = array.flatMap { $0.name }
[“Foo”, “Bar”]
. Но начиная с версии 4.0 строковые значения реализуют протокол Collection. Следовательно, наше применение flatMap
в данном случае вместо третьей перегрузки будет соответствовать первой, и мы получим «сплющенный» результат из преобразованных значений: [“F”, “o”, “o”, “B”, “a”, “r”]
flatMap
вы не получите ошибку, потому что это разрешённое использование. Но логика окажется нарушенной, поскольку результат относится к типу Array<Character>.Type
, а не к ожидаемому Array<String>.Type
.flatMap
, из новой версии Swift убрана третья перегруженная версия. А для решения той же задачи (удаления nil-значений) теперь нужно использовать отдельный метод — compactMap
.
К сожалению, не доступен сервер mySQL