The original PR thread cites the following snippet as an example of the potential Swift 3 incompatibility that's being worked around with the String? overload:
struct S { var p: String }
func f(xs: [S]) -> [String] {
return xs.flatMap { $0.p }
}
Since Strings are now Collections, the String? overload is potentially needed to prevent flatMap from returning a flattened array of Characters.
But if I understand the details of flatMap() correctly (which I quite possibly do not), this seems like something of a straw man example. flatMap() wants a sequence of consistent types, and it unwraps only one level of sequencing. So if you were dealing with a sequence of arrays of strings---which I would imagine is a more typical application for flatMap here---it would continue to work in Swift 4 just exactly as it did in Swift 3, even without the String? overload.
let woids = [["one", "two", "three"], [], ["four"]]
woids.flatMap { $0 } // [ "one", "two", "three", "four" ]
If in Swift 3 you were calling flatMap() to process a simple sequence of Strings (as in the PR example above), you really shouldn't have been; the intended result is no different from map() in that context.
There are clearly a lot of nuances here, but I can't help thinking that the introduction of compactMap() really ought to help resolve all of this. In particular, why is the String? overload being dragged along into 4.1 for compactMap()? There's no legacy code that calls compactMap, and strings-as-collections shouldn't be an issue either since compactMap() does not expand sequences.
If you replace flatMap() with map() in this example, the compiler doesn't know what to make of it and gives a pretty clear description of the problem:
// ERROR: Unable to infer complex closure return type; add explicit type to disambiguate
let x = [0, 1].map { x in
if String(x) == "foo" {
return "bar"
}
return nil
}
That seems reasonable. Why shouldn't flatMap() and compactMap() behave identically to map() in this regard? It's not really a flatMap issue so much as a gap in the current type inference system. Under current rules, this closure requires an explicit return-type declaration.
Special handling for String leads directly to problems with an Int version of this same code (or at least, I'd argue that the error message is problematic):
let x = [0, 1].flatMap { x in
if x == 1 {
// ERROR: Cannot convert return expression of type 'Int' to return type 'String?'
return 1
}
return nil
}