Both map
and flatMap
are defind on ImplicitlyUnwrappedOptional
, but they differ (obviously) in their definition according to the documentation:
func map(f: @noescape (T) -> U) -> U!
If self == nil, returns nil. Otherwise, returns f(self!).
func flatMap(f: @noescape (T) -> U!) -> U!
Returns f(self)! iff self and f(self) are not nil.
I tried using them with a simple example:
let number: Int? = 1
let res1 = number.map { $0 + 1 }.map { $0 + 1 }
let res2 = number.flatMap { $0 + 1 }.flatMap { $0 + 1 }
res1 //3
res2 //3
But they produced the same results even if number
was nil.
So my question is, what is the actual difference between them if I apply map
or flatMap
to ImplicitlyUnwrappedOptional
s? Which one should I choose over the other and when?
(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With