Map and Flatmap Difference in Optional Unwrapping in Swift 1.2

Map and flatMap difference in optional unwrapping in Swift 1.2

(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 with Optional Unwrapping in Swift

compactMap() can do this for you in one step:

let paths:[String?] = ["test", nil, "Two"]

let nonOptionals = paths.compactMap{$0}

nonOptionals will now be a String array containing ["test", "Two"].

Previously flatMap() was the proper solution, but has been deprecated for this purpose in Swift 4.1

Difference between flatMap and compactMap in Swift

The Swift standard library defines 3 overloads for flatMap function:

Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]  
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]

The last overload function can be misused in two ways:

Consider the following struct and array:

struct Person {
var age: Int
var name: String
}

let people = [
Person(age: 21, name: "Osame"),
Person(age: 17, name: "Masoud"),
Person(age: 20, name: "Mehdi")
]

First Way: Additional Wrapping and Unwrapping:

If you needed to get an array of ages of persons included in people array you could use two functions :

let flatMappedAges = people.flatMap({$0.age})  // prints: [21, 17, 20]
let mappedAges = people.map({$0.age}) // prints: [21, 17, 20]

In this case the map function will do the job and there is no need to use flatMap, because both produce the same result. Besides, there is a useless wrapping and unwrapping process inside this use case of flatMap.(The closure parameter wraps its returned value with an Optional and the implementation of flatMap unwraps the Optional value before returning it)

Second Way - String conformance to Collection Protocol:

Think you need to get a list of persons' name from people array. You could use the following line :

let names = people.flatMap({$0.name})  

If you were using a swift version prior to 4.0 you would get a transformed list of

["Osame", "Masoud", "Mehdi"]  

but in newer versions String conforms to Collection protocol, So, your usage of flatMap() would match the first overload function instead of the third one and would give you a flattened result of your transformed values:

["O", "s", "a", "m", "e", "M", "a", "s", "o", "u", "d", "M", "e", "h", "d", "i"]

Conclusion: They deprecated third overload of flatMap()

Because of these misuses, swift team has decided to deprecate the third overload to flatMap function. And their solution to the case where you need to to deal with Optionals so far was to introduce a new function called compactMap() which will give you the expected result.

How to use swift flatMap to filter out optionals from an array

Since Swift 4.1 you can use compactMap:

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.compactMap { $0 }

(Swift 4.1 replaced some overloads of flatMap with compactmap.
If you are interested in more detail on this then see for example:
https://useyourloaf.com/blog/replacing-flatmap-with-compactmap/
)

With Swift 2 b1, you can simply do

let possibles:[Int?] = [nil, 1, 2, 3, nil, nil, 4, 5]
let actuals = possibles.flatMap { $0 }

For earlier versions, you can shim this with the following extension:

extension Array {
func flatMap<U>(transform: Element -> U?) -> [U] {
var result = [U]()
result.reserveCapacity(self.count)
for item in map(transform) {
if let item = item {
result.append(item)
}
}
return result
}
}

One caveat (which is also true for Swift 2) is that you might need to explicitly type the return value of the transform:

let actuals = ["a", "1"].flatMap { str -> Int? in
if let int = str.toInt() {
return int
} else {
return nil
}
}
assert(actuals == [1])

For more info, see http://airspeedvelocity.net/2015/07/23/changes-to-the-swift-standard-library-in-2-0-betas-2-5/

How can I call .map() on ImplicitlyUnwrappedOptional?


let i64Val = (iVal  as ImplicitlyUnwrappedOptional).map {UInt64($0)}

About using Swiftz library issues(Functional programing)

Person().walk is a function - (Int) -> Void, and 10 is an Int. What <*> does is function application - it applies the left operand to the function that is the right operand. In other words, it calls the function on its right with the thing on its left as an argument. It's just that both the function and its argument can be optional (note the ?s in the signature). This is kind of like Haskell's <*>, but specifically for Maybe (Optional's Haskell counterpart).

If you remove all the optional-handling:

public func <*> <A, B>(f : ((A) -> B), a : A) -> B {
return f(a)
}

Yep, that's what it does (plus handling the optionals)!

The optional handling isn't that hard to understand either. $0 <^> a just means a.map($0). Both flatMap. These are built in Swift functions. If you don't understand how they work, read this. Ultimately what it does is if either f or a is nil, <*> returns nil too.

Now you might be wondering why we need this function at all. Well, making "apply this function" itself a function means that we can pass it around to other functions, compose it with other functions!

However, I don't think this works in Swift as well as it does in Haskell. In Swift, the compiler needs to be able to infer every type parameter to a known type, which can make many things that you can easily do in Haskell, difficult and cumbersome.

How to unwrap the elements of an Array in Swift? (ie. Array Int? as Array Int )

update: Xcode 7.2 • Swift 2.1.1

let arrayOfStrings = ["0", "a", "1"]
let numbersOnly = arrayOfStrings.flatMap { Int($0) }

print(numbersOnly) // [0,1]

Unwrap Sparse Array in Swift

You can't do this right now in Swift. To add that function as an extension to Array, you'd have to mark somehow that it's only callable with certain kinds of arrays: those with optional values as the subtype. Unfortunately, you can't further specialize a generic type, so global functions are the only way possible.

This is the same reason Array has a sort method that takes a comparison function as a parameter, but it doesn't have a sort that "just works" if the array is full of Comparable members - to get that kind of function, you have to look at the top-level sort:

func sort<T : Comparable>(inout array: [T])

Swift Optional type: how .None == nil works

enum Optional conforms to the NilLiteralConvertible protocol,
which means that it can be initialized with the "nil" literal.
The result is Optional<T>.None where the type placeholder T
must be inferred from the context.

As an example,

let n = nil // type of expression is ambiguous without more context

does not compile, but

let n : Int? = nil

does, and the result is Optional<Int>.None.

Now optionals can in general not be compared if the underlying
type is not Equatable:

struct ABC { }

let a1 : ABC? = ABC()
let a2 : ABC? = ABC()

if a1 == a2 { } // binary operator '==' cannot be applied to two 'ABC?' operands

and even this does not compile:

if a1 == Optional<ABC>.None { } // binary operator '==' cannot be applied to two 'ABC?' operands

But this compiles:

if a1 == nil { } 

It uses the operator

public func ==<T>(lhs: T?, rhs: _OptionalNilComparisonType) -> Bool

where _OptionalNilComparisonType is not documented officially.
In https://github.com/andelf/Defines-Swift/blob/master/Swift.swift the
definition can be found as (found by @rintaro and @Arsen, see comments):

struct _OptionalNilComparisonType : NilLiteralConvertible {
init(nilLiteral: ())
}

This allows the comparison of any optional type with "nil", regardless of whether the underlying type is Equatable or not.

In short – in the context of Optionalnil can be thought of as a shortcut to .None, but the concrete type must be inferred from the context. There is a dedicated == operator for comparison with "nil".



Related Topics



Leave a reply



Submit