Yakiv Kovalskyi
Yakiv Kovalskyi

Reputation: 1757

compactMap result is of weird type

I expect allMembers to be of type [Member]. But instead its type is [[Member]]. Why compactMap does not return result of [Member] type?

class Team {
    let members = Array(repeating: Member(), count: 2)
}

class Member {
}

let teams = Array(repeating: Team(), count: 3)
let allMembers = teams.compactMap { $0.members }

Upvotes: 2

Views: 1796

Answers (2)

Ankit Jayaswal
Ankit Jayaswal

Reputation: 5669

Adding some detail in @David's answer:

Using flatMap on a sequence (like an Array) filtering anything that maps to nil is now deprecated and replaced by compactMap.

Using flatMap on a sequence with a closure that returns an optional.

Sequence.flatMap<U>(_ transform: (Element) -> U?) -> U?

For example:

Rather than:

let names: [String?] = ["Tom", nil, "Peter", nil, "Harry"]
let valid = names.flatMap { $0 }

You have to use:

let valid = names.compactMap { $0 }
// ["Tom", "Peter", "Harry"]

let names: [String?] = ["Tom", nil, "Peter", nil, "Harry"]
let counts = names.compactMap { $0?.count }
// [3, 5, 5]

But again, Swift 4.1 does not deprecate all uses of flatMap - out of 3 cases, only one case is changing. Below two situations are as it is where you need to use flatMap:

1) Using flatMap on a sequence with a closure that returns a sequence:

Sequence.flatMap<S>(_ transform: (Element) -> S) -> [S.Element] where S : Sequence

Example:

let scores = [[5,2,7], [4,8], [9,1,3]]
let allScores = scores.flatMap { $0 }
// [5, 2, 7, 4, 8, 9, 1, 3]

let passMarks = scores.flatMap { $0.filter { $0 > 5} }
// [7, 8, 9]

2) Using flatMap on an optional: The closure takes the non-nil value of the optional and returns an optional. If the original optional is nil then flatMap returns nil:

Optional.flatMap<U>(_ transform: (Wrapped) -> U?) -> U?

Example:

let input: Int? = Int("8")
let passMark: Int? = input.flatMap { $0 > 5 ? $0 : nil}
// Optional(8)

For more detail, please refer here

Upvotes: 0

David Pasztor
David Pasztor

Reputation: 54706

You actually need flatMap, not compactMap.

Even though previously (before Swift 4.1), compactMap was also called flatMap, it had a different implementation and function signature than the current flatMap, since compactMap can be used instead of consecutive map and filter calls to map each element to a new element while only keeping non-nil elements. On the other hand, flatMap flattens out nested lists while mapping elements.

This is the still existing flatMap on Sequence, while this is the deprecated flatMap on Sequence that was renamed to compactMap. As you can see, the function signature of the renamed version was

func flatMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

, so its closure input argument returned an Optional value (just like compactMap does now), while the still existing flatMap has a function signature

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

, which doesn't return an Optional in its closure.

You should use the non-deprecated flatMap to flatten out your nested Array<Array<Member>>.

let allMembers = teams.flatMap { $0.members }

Upvotes: 3

Related Questions