[swift-evolution] Reconsidering the (Element -> T?) variant of SequenceType.flatMap

Andy Matuschak andy at andymatuschak.org
Fri Dec 4 17:26:58 CST 2015

Hello, all! This SequenceType-implemented flatMap recently caused some confusion on my team:

func flatMap<T>(@noescape transform: (Self.Generator.Element) throws -> T?) rethrows -> [T]

I’m a big fan of this operator in various functional libraries, but I admit I was a bit surprised to see the “flatMap” terminology appear in the Swift stdlib in the first place—its naming is certainly a notch obscure!

From the reactions of teammates in code reviews involving these methods, there was a significant difference in comprehensibility between the Element -> [T] variant and the Element -> T? variant. The former was easily explained by “it’s a map, followed by a flatten,” whereas the same explanation failed in the latter case.

I expect that the inspiration came from Scala, where the equivalent definition has a transformer essentially of type  Element -> GeneratorType<T>; separately, their optionals are implicitly convertible to (their equivalent of) GeneratorType. So, in the end, in Scala, you can effectively flatMap with an Element -> T? transformer.

But Optional doesn’t implement GeneratorType, and I’d (weakly) argue that it shouldn’t. And if we think about flatMap in the context of a monadic bind (I do, anyway!), it’s especially surprising that the transformer is operating in a different monadic context (Optional) than the receiver (SequenceType). Unless we made Optional adopt SequenceType, in which case we could consider the bind to be happening in that context.

In conclusion, I argue that this overload is confusing both to folks unfamiliar with FP (because it doesn’t feel like Optionals can be flattened) and to folks familiar with FP (because it implies binding across monadic contexts).


In terms of what to do instead: I do think that this is a useful method, and I’d like to keep this functionality easily accessible! Two ideas:

1. We expose a separate operator like:

extension SequenceType where Generator.Element: OptionalType {
	func filterNils() -> [Generator.Element.Wrapped]

// To deal with limitations on protocol extension type restriction:
protocol OptionalType {
	typealias Wrapped
	func flatMap<Result>(@noescape f: Wrapped -> Result?) -> Result?
extension Optional: OptionalType {}

Clients would do myArray.map(optionalReturningTransform).filterNils(). There would be some performance impact from the intermediate array.

2. We give the foo variant a more specific name, e.g. mappedArrayFilteringNils etc. Naming this is tricky (which probably implies it should be decomposed?).
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151204/0b5b792e/attachment.html>

More information about the swift-evolution mailing list