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 String
s are now Collection
s, the String?
overload is potentially needed to prevent flatMap
from returning a flattened array of Character
s.
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 String
s (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
}