2

I'd like to write an extension for Array which safely returns an unwrapped version of itself.

I can do it with a generic method like so:

func unwrapElements<T>(array: [T?]) -> [T] {
    let filtered: [T?] = array.filter{ $0 != nil }
    let unwrapped: [T] = filtered.map { $0! }
    return unwrapped
}

And I can call it like this:

let sparseNames: [String?] = ["alice", "bob", nil, "doug", nil, nil, "george", "hubert"]
let names: [String] = unwrapElements(sparseNames)

where names ends up being ["alice", "bob", "doug", "george", "hubert"] and is safe to iterate and work with each element.

However, I want to call it like this:

let names = sparseNames.unwrapElements()

I've seen a few similar questions (like this one) but they don't address how to create the method as an extension.


(this is tagged with Xcode6.1 to denote the version of Swift I'm using)


Note: Swift 1.2 Beta 3 has introduced the flatMap function which helps with optionally chaining arrays. See this excellent blog post here

Community
  • 1
  • 1
Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60

3 Answers3

3

You can't do this right now in Swift. To add that function as an extension to Array, you'd have to mark somehow that it's only callable with certain kinds of arrays: those with optional values as the subtype. Unfortunately, you can't further specialize a generic type, so global functions are the only way possible.

This is the same reason Array has a sort method that takes a comparison function as a parameter, but it doesn't have a sort that "just works" if the array is full of Comparable members - to get that kind of function, you have to look at the top-level sort:

func sort<T : Comparable>(inout array: [T])
Nate Cook
  • 92,417
  • 32
  • 217
  • 178
  • well boo. This [question](http://stackoverflow.com/questions/25064644/how-to-determine-if-a-generic-is-an-optional-in-swift) seemed to indicate there might be a method of doing this. – Stephen Furlani Nov 24 '14 at 14:24
1

Have you tried using filter and map for that?

let array: [String?] = ["Hello", nil, "World"]

let unwrapped = array.map{$0 ?? nil}.filter{$0 != nil}.map{$0!}

println("unwrapped: \(unwrapped)")
// prints "unwrapped: [Hello, World]"

The first map uses the Nil Coalescing Operator to unwrap if possible. Although, I return nil regardless since the following filter removes all nil values. The last map does the actual unwrapping.

  • So this is interesting, if you perform each of those steps on separate lines, the `Optional()` is never removed. you need to add an `as! [String]` after the filtered line, which must be implied in the final `.map` ? – Stephen Furlani May 19 '15 at 18:03
  • Do you mean as separate `let x =` statements? I tried that myself and did not have to change anything: `let step1 = array.map{$0 ?? nil} // prints "[Optional("Hello"), nil, Optional("World")]"` `let step2 = step1.filter{$0 != nil} // prints "[Optional("Hello"), Optional("World")]"` `let step3 = step2.map{$0!} // prints [Hello, World]` – Renan Paul Blanco May 19 '15 at 18:57
  • ah, when writing it out I missed the final `$0!` – Stephen Furlani May 20 '15 at 16:54
  • This is exactly what `flatMap` is for. – Miles Egan Jun 13 '17 at 03:44
0

You can do this. Here is how:

extension Array {
    func catOptionals<A>() -> [A] where Element == A? {
        return self.flatMap{ $0 }
    }
}
thetrutz
  • 1,395
  • 13
  • 12