30

It seems like in Swift 4.1 flatMap is deprecated. However there is a new method in Swift 4.1 compactMap which is doing the same thing? With flatMap you can transform each object in a collection, then remove any items that were nil.
Like flatMap

let array = ["1", "2", nil] 
array.flatMap { $0 } // will return "1", "2"

Like compactMap

let array = ["1", "2", nil] 
array.compactMap { $0 } // will return "1", "2"

compactMap is doing the same thing.

What are the differences between these 2 methods? Why did Apple decide to rename the method?

shim
  • 9,289
  • 12
  • 69
  • 108
BilalReffas
  • 8,132
  • 4
  • 50
  • 71

3 Answers3

28

The Swift standard library defines 3 overloads for flatMap function:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]  
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?  
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]  

The last overload function can be misused in two ways:
Consider the following struct and array:

struct Person {
  var age: Int
  var name: String
}  

let people = [
  Person(age: 21, name: "Osame"),
  Person(age: 17, name: "Masoud"),
  Person(age: 20, name: "Mehdi")
]

First Way: Additional Wrapping and Unwrapping:
If you needed to get an array of ages of persons included in people array you could use two functions :

let flatMappedAges = people.flatMap({$0.age})  // prints: [21, 17, 20]
let mappedAges = people.map({$0.age})  // prints: [21, 17, 20]  

In this case the map function will do the job and there is no need to use flatMap, because both produce the same result. Besides, there is a useless wrapping and unwrapping process inside this use case of flatMap.(The closure parameter wraps its returned value with an Optional and the implementation of flatMap unwraps the Optional value before returning it)

Second Way - String conformance to Collection Protocol:
Think you need to get a list of persons' name from people array. You could use the following line :

let names = people.flatMap({$0.name})  

If you were using a swift version prior to 4.0 you would get a transformed list of

["Osame", "Masoud", "Mehdi"]  

but in newer versions String conforms to Collection protocol, So, your usage of flatMap() would match the first overload function instead of the third one and would give you a flattened result of your transformed values:

["O", "s", "a", "m", "e", "M", "a", "s", "o", "u", "d", "M", "e", "h", "d", "i"]

So, How did they solve it? They deprecated third overload of flatMap()
Because of these misuses, swift team has decided to deprecate the third overload to flatMap function. And their solution to the case where you need to to deal with Optionals so far was to introduce a new function called compactMap() which will give you the expected result.

Mehdi Ijadnazar
  • 4,532
  • 4
  • 35
  • 35
  • 1
    Great explanation! Incidentally, the `let mappedAges = people.map({$0.name})` line is using `$0.name` instead of `$0.age` – Toni Sučić Sep 29 '18 at 14:11
20

There are three different variants of flatMap. The variant of Sequence.flatMap(_:) that accepts a closure returning an Optional value has been deprecated. Other variants of flatMap(_:) on both Sequence and Optional remain as is. The reason as explained in proposal document is because of the misuse.

Deprecated flatMap variant functionality is exactly the same under a new method compactMap.

See details here.

aashish tamsya
  • 4,903
  • 3
  • 23
  • 34
Bilal
  • 18,478
  • 8
  • 57
  • 72
  • 2
    @Krin-San You missed the point. There are three different variants of flatMap. The one accepts *Optional Value* has beed deprecated only. So from your example flatMap you are using is different method and is not deprecated. Change you scores to this `let scores:[[Int]?] = [[5,2,7], [4,8], [9,1,3]]` and you will see the deprecated warning and also the same results as you are getting using 'compactMap'. – Bilal Jul 16 '18 at 06:21
0

High-order function - is a function which operates by another function in arguments or/and returned. For example - sort, map, filter, reduce...

map vs compactMap vs flatMap

[RxJava Map vs FlatMap]

  • map - transform(Optional, Sequence, String)
  • flatMap - flat difficult structure into a single one(Optional, Collection)
  • compactMap - next step of flatMap. removes nil

flatMap vs compactMap

Before Swift v4.1 three realisations of flatMap had a place to be(without compactMap). That realisation were responsible for removing nil from a sequence. And it were more about map than flatMap

Experiments

//---------------------------------------
//map - for Optional, Sequence, String, Combine
//transform

//Optional
let mapOptional1: Int? = Optional(1).map { $0 } //Optional(1)
let mapOptional2: Int? = Optional(nil).map { $0 } //nil
let mapOptional3: Int?? = Optional(1).map { _ in nil } //Optional(nil)
let mapOptional4: Int?? = Optional(1).map { _ in Optional(nil) } //Optional(nil)

//collection
let mapCollection1: [Int] = [1, 2].map { $0 } //[1, 2]
let mapCollection2: [Int?] = [1, 2, nil, 4].map { $0 } //Optional(1), Optional(2), nil, Optional(4),
let mapCollection3: [Int?] = ["Hello", "1"].map { Int($0) } //[nil, Optional(1)]

//String
let mapString1: [Character] = "Alex".map { $0 } //["A", "l", "e", "x"]




//---------------------------------------
//flatMap - Optional, Collection, Combime

//Optional
let flatMapOptional1: Int? = Optional(1).flatMap { $0 } //Optional(1)
let flatMapOptional2: Int? = Optional(nil).flatMap { $0 } //nil
let flatMapOptional3: Int? = Optional(1).flatMap { _ in nil }
let flatMapOptional4: Int? = Optional(1).flatMap { _ in Optional(nil) }

//Collection
let flatMapCollection1: [Int] = [[1, 2], [3, 4]].flatMap { $0 } //[1, 2, 3, 4]
let flatMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].flatMap { $0 } //DEPRECATED(use compactMap): [[1, 2], [3, 4]]
let flatMapCollection3: [Int?] = [[1, nil, 2], [3, 4]].flatMap { $0 } //[Optional(1), nil, Optional(2), Optional(3), Optional(4)]
let flatMapCollection4: [Int] = [1, 2].flatMap { $0 } //DEPRECATED(use compactMap):[1, 2]




//---------------------------------------
//compactMap(one of flatMap before 4.1) - Array, Combine
//removes nil from the input array

//Collection
let compactMapCollection1: [Int] = [1, 2, nil, 4].compactMap { $0 } //[1, 2, 4]
let compactMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].compactMap { $0 } //[[1, 2], [3, 4]]

[Swift Optional map vs flatMap]
[Swift Functor, Applicative, Monad]

yoAlex5
  • 29,217
  • 8
  • 193
  • 205