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

3 Answers 3

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])
Sign up to request clarification or add additional context in comments.

1 Comment

well boo. This question seemed to indicate there might be a method of doing this.
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.

4 Comments

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 ?
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]
ah, when writing it out I missed the final $0!
This is exactly what flatMap is for.
0

You can do this. Here is how:

extension Array {
    func catOptionals<A>() -> [A] where Element == A? {
        return self.flatMap{ $0 }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.