I find ignoring the implementations and focus on the type signatures helpful. There are also several map
s and flatMap
s in the standard library and frameworks that may help your understanding. The type signature of most of them is similar and imply some commonalities.
Note: I'm will use Optional<A>
for A?
, Array<A>
for [A]
as (I find it) easier to see the similarities.
map
The type signature of Optional<Wrapped>.map
is
extension Optional {
func map<U>(
_ transform: (Wrapped) -> U
) -> Optional<U>
}
Similarly, the type signature of Array<Element>.map
is
extension Array {
func map<U>(
_ transform: (Element) -> U
) -> Array<A>
}
The type signature of Result<Success, Failure>.map
is
extension Result {
func map<U>(
_ transform: (Success) -> U
) -> Result<U, Failure>
}
All three of these methods have have the same structure. They take a function from a type parameter (Wrapped
, Element
, Success
) to a generic (U
), and they return a new value of type Optional<U>
, Array<U>
, Result<U, Failure>
. More succinctly
Optional<Wrapped>.map + ((Wrapped) -> U) -> Optional<U>
Array<Element>.map + ((Element) -> U) -> Array<U>
Result<Success, Failure>.map + ((Success) -> U) -> Result<U, Failure>
Each of these generic types that looks like F<A>
, where F = Optional
, F = Array
, or F = Result<_, Failure>
. And their map
s have the following type signature
F<A>.map + ((A) -> B) -> F<B>
That is, you can think of map
as preserving the structure of your generic while changing the values in the container.
let x: Optional<Int> = .some(2) // or 2
let y: Optional<Int> = nil
let xs: [1,2,3]
let success: Result<Int, Error> = .success(2)
let failure: Result<Int, Error> = .failure(anError)
x.map { $0 + 1 } == 3
y.map { $0 + 1 } == nil
xs.map { $0 + 1 } == [2,3,4]
success.map { $0 + 1 } == .success(3)
failure.map { $0 + 1 } == .failure(anError)
Notice that the structure didn't change. If I started with .some
, map kept the same structure but changed values by incrementing them by 1. Similarly for the other values.
flatMap
We have slightly different type signatures for flatMap
:
extension Optional {
func flatMap<T>(
_ transform: (Wrapped) -> Optional<T>
) -> Optional<T>
}
extension Array {
func flatMap<T>(
_ transform: (Element) -> Array<T>
) -> Array<T>
}
extension Result {
func flatMap<T>(
_ transform: (Success) -> Result<T, Failure>
) -> Result<T, Failure>
}
Notice difference in the type signatures. map
's closure has type (A) -> B
and flatMap
's closure has type (A) -> F<B>
where F
corresponds to Optional
, Array
or Result<_, Failure>
.
Before looking at what flatMap
does to particular values, let see what map
does
let f: (Int) -> Optional<Int> = {
$0.isMultiple(of: 2)
? .some($0 + 1)
: nil
}
let x: Optional<Int> = .some(2)
let y: Optional<Int> = .some(3)
let z: Optional<Int> = nil
x.map(f) == .some(.some(3))
y.map(f) == .some(.none)
z.map(f) == .none
let xs: Array<Int> = [1,2,3]
xs.map { Array(repeating: $0, count: $0) } == [[1],[2,2],[3,3,3]]
let g: (Int) -> Result<Int, Error> = {
$0.isMultiple(of: 2)
? .success($0 + 1)
: .failure(Error.evenNumbersOnly)
}
let success2: Result<Int, Error> = .success(2)
let success3: Result<Int, Error> = .success(3)
let failure: Result<Int, Error> = .failure(anError)
success2.map(g) == .success(.success(3))
success3.map(g) == .success(.failure(Error.evenNumbersOnly))
failure.map(g) == .failure(anError)
What about flatMap
?
x.flatMap(f) == .some(3)
y.flatMap(f) == nil
z.flatMap(f) == nil
let xs: Array<Int> = [1,2,3]
xs.flatMap { Array(repeating: $0, count: $0) } == [1,2,2,3,3,3]
success2.flatMap(g) == .success(3)
success3.flatMap(g) == .failure(Error.evenNumbersOnly)
failure.flatMap(g) == .failure(anError)
So map
yielded nested values of type F<F<B>>
and flatMap
yielded F<B>
. In fact, you can think of flatMap
as map
then join
, where join
is a function with the following type signatures
func join<U>(_ x: Optional<Optional<U>>) -> Optional<U>
func join<U>(_ x: Array<Array<U>>) -> Array<U>
func join<U>(_ x: Result<Result<U, Failure>, Failure>) -> Result<U, Failure>
That is,
join(optionalValue.map(f)) == optionalValue.flatMap(f)
join(array.map(f)) == array.flatMap(f)
join(result.map(f)) == array.flatMap(f)