Map and flatMap difference in optional unwrapping in Swift 1.2
bind, dictionary, flatmap, option-type, swift
Solution
(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.
Problem
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?