String was extended in beta 6 to implement RangeReplaceableCollectionType . This means that, via inheritance, it also implements ExtensibleCollectionType .1

ExtensibleCollectionType is interesting, because it requires the collection to support an empty initializer. This means that, without having to resort to shenanigans, you can write a generic function that takes an ExtensibleCollectionType and returns a new one.

Since they were changed to return eagerly-evaluated results, the non-member filter and map have returned arrays, no matter what. This is a bit frustrating when working with some non-array types, such as String :2

let vowels = "eaoiu" let isConsonant = { !contains(vowels, $0) } let s = "hello, i must be going" // filtered will be an array let filtered = filter(s, isConsonant) // and then we have to turn it back into a string let only_consonants = String(seq: filtered) // only_consonants is "hll, mst b gng"

It would be nice to have a version of filter that took a String and returned a String instead of an Array . 3 Even better, it would be nice to have a single generic version that worked on both arrays and strings.

Here’s one:

func my_filter <C: ExtensibleCollectionType> (source: C, includeElement: (C.Generator.Element)->Bool) -> C { // use the `init()` from `ExtensibleCollectionType` var result = C() for element in source { if(includeElement(element)) { // append is also part of `ExtensibleCollectionType` result.append(element) } } return result } // my_filter returns a String when passed one: let only_consonants = my_filter(s, isConsonant)

Since this is possible, should Swift’s filter and map be changed to be like this? Maybe, but I can think of a couple of reasons why not.

First, it’d be a bit inconsistent and possibly surprising. Not all collections are extensible collections. Dictionary isn’t. Range and StrideTo even less so – they’re like “virtual” collections that don’t really have individual elements at all. So there’d still need to be versions that took these collections and returned an array. So when calling filter , you’d need to know whether your collection was extensible to know whether you were going to get back the same collection type or an array.

There’s precedent for this kind of thing. lazy gives you back different types depending on what you pass in. But lazy is very explicit. map and filter would be a bit more subtle, and bear in mind subtle maybe-unexpected behaviour was probably the reason lazy evaluation was moved into the lazy family in the first place.

Second, maybe you do want an array back. This can be catered for – declare a second version of my_filter like so: 4

func my_filter <C1: ExtensibleCollectionType, C2: ExtensibleCollectionType where C1.Generator.Element == C2.Generator.Element> (source: C1, includeElement: (C1.Generator.Element)->Bool) -> C2 { var result = C2() for element in source { if(includeElement(element)) { result.append(element) } } return result } // same-type version will be used by default let consonant_string = my_filter(s, isConsonant) // but if you declare the result as a specific type, the // second version will be used: let consonant_array = my_filter(s, isConsonant) as Array

Third, there’s the big gotcha that means this wouldn’t be a good idea, but that I haven’t thought of. If you have, leave a comment or tweet me.