Hi, Habr!
My name is Alexander Zimin, I am an iOS developer at Badoo. This is a translation of an article by my colleague Schwib, in which he explained what the flatMap function in Swift was and why one of its overloads was renamed compactMap. The article is useful both for understanding the processes occurring in
the Swift repository and
its evolution , as well as for general development.

In functional programming, there is a clear definition of what the
flatMap
function
flatMap
. The
flatMap
method takes a list and a conversion function (which expects to receive zero or more values for each transformation), applies it to each element of the list and creates a flattened list. This behavior is different from the simple
map
function, which applies a transformation to each value and expects to receive only one value for each conversion.

Already for several versions in Swift there are
map
and
flatMap
. However, in
Swift 4.1, you can no longer apply
flatMap
to a sequence of values and at the same time pass a closure that returns an optional value. There is now a
compactMap
method for
compactMap
.
At first it may not be so easy to understand the essence of innovation. If
flatMap
worked well, why introduce a separate method? Let's figure it out.
The Swift standard library up to version 4.1 provided three
flatMap
overload
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]
Let's go through all three options and see what they do.
Sequence.flatMap <S> (_: (Element) -> S) -> [S.Element], where S: Sequence
The first overload is for sequences in which the closure takes an element of this sequence and converts it into another sequence.
flatMap
all these transformed sequences into the final sequence returned as a result. For example:
let array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] let flattened = array.flatMap { $0 }
This is a great example of how the
flatMap
method should work. We transform (map) each element of the original list and create a new sequence. Thanks to
flatMap
end result is a flattened structure of transformed sequences.
Optional.flatMap <U> (_: (Wrapped) -> U?) -> U?
The second overload is for optional types. If the optional type you are calling has a value, then the closure will be called with a value without an optional wrapper (unwrapped value), and you can return the converted optional value.
let a: Int? = 2 let transformedA = a.flatMap { $0 * 2 }
Sequence.flatMap <U> (_: (Element) -> U?) -> [U]
The third overload will help you understand what
compactMap
. This version looks the same as the first, but there is an important difference. In this case, the closure returns an optional.
flatMap
processes it by passing the returned nil values, and includes all the rest in the result as values without a wrapper.
let array = [1, 2, 3, 4, nil, 5, 6, nil, 7] let arrayWithoutNils = array.flatMap { $0 }
But in this case, the ordering is not performed. Therefore, this version of
flatMap
closer to the
map
than the purely functional definition of
flatMap
. And the problem with this overload is that you can misuse it where the
map
would work fine.
let array = [1, 2, 3, 4, 5, 6] let transformed = array.flatMap { $0 }
This
flatMap
application corresponds to the third overload, implicitly wrapping the transformed value in optional, and then removing the wrapper to add to the result. The situation becomes especially interesting if you incorrectly use the transformation of string values.
struct Person { let name: String } let people = [Person(name: “Foo”), Person(name: “Bar”)] let names = array.flatMap { $0.name }
In Swift to version 4.0, we would get a conversion to
[“Foo”, “Bar”]
. But since version 4.0, string values implement the Collection protocol. Therefore, our use of
flatMap
in this case instead of the third overload will correspond to the first, and we will get a “flattened” result from the transformed values:
[“F”, “o”, “o”, “B”, “a”, “r”]
When you call
flatMap
you will not get an error, because it is an allowed use. But the logic will be broken, because the result is of type
Array<Character>.Type
, and not of the expected
Array<String>.Type
.
Conclusion
To avoid improper use of
flatMap
, the third overloaded version is removed from the new version of Swift. And to solve the same problem (delete nil-values), now you need to use a separate method -
compactMap
.