[Pitch] Deprecate `Optional.map(_:)`

Optional.map(_:) is a great way to perform lots of operations that cannot make use of Optional Chaining syntactic sugar. However, it is identical to Optional.flatMap(_:) except for the limitation that map's closure must return a nonoptional value. Basically, map is a specialized version of flatMap that performs the exact same function, removing the need for it to be specialized.

I propose deprecating Optional.map(_:) in favor of Optional.flatMap(_:). This does not break ABI stability. Source compatibility is not broken either, but the automatic migrator should convert all instances of map to flatMap to avoid deprecation warnings.

The existence of two entirely separate methods for this behavior is confusing and unnecessary. Any code that uses map can already be converted to flatMap with no impact on functionality (but not vice versa).

An alternative to this is to deprecate flatMap and make map behave like flatMap.

It could be argued that it would be an abuse of Swift's automatic migration of T to T? where an optional is expected

3 Likes

One thing I'd like to see along with this is a case study of how map and flatMap are used currently. If there's evidence of misuse in current code, this this might make sense. Otherwise it would probably be too large of a source break at this point.

@AlexanderM Could you please elaborate? I'm not sure exactly what you're referring to.

@saagarjha For example usages, see the linked documentation for those two functions. The proposal is not to remove map, just to deprecate it to unify the two functions. They do exactly the same thing, and map is just a specialized version of flatMap. The specialization is unnecessary because the map does not change any behavior; map simply requires its closure to return a nonoptional value, but then returns an optional even if its closure does run and return.

I'm talking about usage by developers, pulled from say GitHub. If we see people using flatMap where map would have sufficed it provides credibility to your claim that map is unnecessary.

@saagarjha That's a great point. The only problem is that map and flatMap are also functions of every Sequence (and thereby every Collection), and those functions are used much more often, making GitHub search near impossible.

You should be aware that the bar for removal (or deprecation) of an existing API at this stage of Swift Evolution requires empiric demonstration of active harm. A proposal to make such a change will not be successful without such evidence.

2 Likes
let o = Optional(42)

print(o.map { $0 > 0 ? $0 : nil })
print(o.flatMap { $0 > 0 ? $0 : nil })

outputs:

Optional(Optional(42))
Optional(42)

I'm pretty sure there is similar code in the wild.

Even worse, due to the way closures are typechecked, the following snippet:

let o = Optional(42)

let result1 = o.map { x in
    if x > 0 {
        return x
    }
    return nil
}
print(result1)

let result2 = o.flatMap { x in
    if x > 0 {
        return x
    }
    return nil
}
print(result2)

does not compile, producing:

error: unable to infer complex closure return type; add explicit type to disambiguate
let result2 = o.flatMap { x in
                        ^
                            -> (Int) 

3 Likes

The first is expected, the second a common issue with multi line closures.

I fail to see your point here. My examples were meant to show that map cannot be substituted for flatMap in all the cases.

1 Like

How to dislike this proposal?

5 Likes

I thought you were trying to illustrate the danger required for this to be considered for removal.

if we’re tweaking these functions, can we please change the remaining flatMap function to some different name. map and flatMap are horrible names.

map makes sense, but flatMap should likely be renamed to compactMap to match the change on Sequence in Swift 4.1. I'm not sure the typical flatMap has a place on Optional.

You can voice your concerns by replying…

okay but how is it a map though. there’s only one object

That's enough to perform a mapping operation…

2 Likes

by that argument every operation is a mapping

I don't think that's right. The Sequence.flatMap that takes an (Element) -> T? closure was renamed to compactMap to disambiguate from the monadic flatMap. Optional.flatMap is exactly this monadic flatMap, so renaming it to compactMap would not make sense.

6 Likes

These functions and their names are well-established over many programming languages, math, and other disciplines, so renaming them to something else in Swift would only cause more confusion and is unlikely.

In math, "map" means "function", so yep! And a map (function) has a domain (argument) and codomain (return value).

6 Likes