(Remark: The answer has been updated to reflect the syntax changes in Swift 3 and later, such as the abolishment of ImplicitlyUnwrappedOptional
.)
Optional.map()
and Optional.flatMap()
are declared as follows (I have omitted the throws/rethrows modifiers which are irrelevant here):
func map<U>(_ transform: (Wrapped) -> U) -> U?
func flatMap<U>(_ transform: (Wrapped) -> U?) -> U?
Let's consider a simplified version of your first example using “map”:
let number: Int? = 1
let res1 = number.map { $0 + 1 }
print(res1) // Optional(2)
number
has the type Int?
and the closure type is inferred as (Int) -> Int
. U
is Int
, and the type of the return value is Int?
. number
is not nil
, so it is unwrapped and passed 1
is passed to the closure. The closure returns 2
and map
returns Optional(2)
. If number
were nil
then the result would be nil
.
Now we consider a simplified version of your second example with “flatMap”:
let number: Int? = 1
let res2 = number.flatMap { $0 + 1 }
print(res2) // Optional(2)
flatMap
expects a closure of type (Wrapped) -> U?
, but { $0 + 1 }
does not return an optional. In order to make it compile, the compiler converts this to
let res2 = number.flatMap { return Optional($0 + 1) }
Now the closure has type (Int) -> Int?
, and U
is Int
again. Again, number
is unwrapped and passed to the closure. The closure returns Optional(2)
which is also the return value from flatMap
. If number
were nil
or if the closure would return nil
then the result would be nil
.
So there is indeed no difference between these invocations:
let res1 = number.map { $0 + 1 }
let res2 = number.flatMap { $0 + 1 }
However that is not what flatMap
is meant for. A more realistic example would be
func foo(_ s : String?) -> Int? {
return s.flatMap { Int($0) }
}
print(foo("1")) // Optional(1)
print(foo("x")) // nil (because `Int($0)` returns nil)
print(foo(nil)) // nil (because the argument is nil)
Generally, map
takes a closure of type (Wrapped) -> U
and transforms
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> Optional<U>.some(transform(wrapped))
flatMap
takes a closure of type (Wrapped) -> U?
and transforms
Optional<Wrapped>.none --> Optional<U>.none
Optional<Wrapped>.some(wrapped) --> transform(wrapped)
Here transform(wrapped)
can be Optional<U>.none
as well.
If (as in your example) flatMap
is called with a closure which does not return an optional then the compiler converts it to an optional automatically, and there is no difference to map
anymore.
map
andflatMap
can be difficult to discern because they are not strict about the closure type they take. In many cases they can take the same closure and produce the same result. MaybeflatMap
should balk if the closure doesn't produce an Optional. – Latinity