Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map and flatMap difference in optional unwrapping in Swift 1.2

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 ImplicitlyUnwrappedOptionals? Which one should I choose over the other and when?

like image 764
Dániel Nagy Avatar asked Apr 10 '15 08:04

Dániel Nagy


1 Answers

(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.

like image 109
Martin R Avatar answered Sep 28 '22 10:09

Martin R