4

Let's say I want to write a method on Array that either returns a copy of the array if its type is non-optional, or a subarray of unwrapped values if its type is an optional. To do this, I think I need to be able to test whether the Array's type T is an optional type or not. For example, this just returns a copy of the array in either case:

extension Array {        
    func unwrapped() -> Array<T> {
        return filter({
            var x: T? = $0
            return x != nil
        })
    }
}

I understand that if I know I have an array of optionals I could just use filter() and map():

let foo: [String?] = [nil, "bar", "baz"]
let bar: [String] = foo.filter({ $0 != nil }).map({ $0! })

I'm not looking for a solution to that specific problem. Rather I'm wondering if there's a way to determine in an Array extension if its type is optional, which could be useful in a number of different convenience methods.

Christopher Pickslay
  • 17,523
  • 6
  • 79
  • 92

4 Answers4

3

One possible solution is to use global functions instead of extensions; then you can overload with two definitions, one for non-optional and one for optional types:

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

unwrapped([1, 2, 3, 4, 5]) // -> [1, 2, 3, 4, 5]
unwrapped([1, 2, 3, nil, 5]) // -> [1, 2, 3, 5]

I'm unsure whether this is guaranteed to work; it would be interesting if someone can find a case where it breaks or somewhere in the Swift guides that says it's always correct.

See also the discussion in this question.

Community
  • 1
  • 1
jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • Yes, this works to unwrap all the values in the array, which is convenient. Strangely they only work as global functions. If I define them as static functions in an Array extension, calling Array.unwrapped gives the error `Cannot invoke 'unwrapped' with an argument of type ` – Christopher Pickslay Jan 30 '15 at 08:07
  • Unfortunately, something like `extension Array` isn't allowed at this point (or even `extension Array`, though I'm not sure that would allow you to do anything useful). This is an area in which the C++ template system exhibits much more flexibility. I hope Swift will get there someday... – jtbandes Jan 30 '15 at 08:08
  • Another interesting point--calling `unwrapped` only works outside the extension. Calling `unwrapped(self)` from a function in an Array extension always invokes the non-optional version and just returns self. – Christopher Pickslay Jan 30 '15 at 08:46
  • That makes sense, since `T` is a generic parameter inside an extension. You might be able to use techniques from the question I linked to work around that... – jtbandes Jan 30 '15 at 09:27
0

Use reduce:

let unwrapped = foo.reduce([String]()) {
    if let bar = $1 as String? { return $0 + [bar] }
    return $0
}

That will return a copy if the original is non-optional, or a subarray of non-nil unwrapped values if the original is optional. You don't need to know if the array contains optionals in order to do this.

If an array contains at least one value, you can determine if that value is an optional like this:

extension Array {
    func optTest() {
        switch reflect(self[0]).disposition {
            case .Optional:
                println("I am an optional")
            default:
                break
        }
    }
}

You would only need to test the first value. But I haven't figured out how to test an empty array - I suppose you could add an element to it and then test that element...

Aaron Rasmussen
  • 13,082
  • 3
  • 42
  • 43
  • Your reduce approach is equivalent to the filter/map approach, and only works if I know the type of the array. I don't see a way to generalize this approach in an Array extension. – Christopher Pickslay Jan 30 '15 at 07:42
  • `disposition` is interesting. That does appear to work for non-empty arrays. – Christopher Pickslay Jan 30 '15 at 08:01
  • Testing in the REPL, your `unwrap` function seems to return a copy of the array, with optionals not unwrapped. `[nil as String?, "foo"].unwrap() $R5: [String?] = 2 values { [0] = nil [1] = "foo" }` – Christopher Pickslay Jan 30 '15 at 08:13
0

The Array struct can be extended to return its generic type, and then a protocol can be used to test for a typeless Optional.

The example is made for Swift 2, but I believe it should work similarly on previous versions.

protocol OptionalProtocol {}

extension Optional : OptionalProtocol {}

extension Array {
    func elementType() -> Any.Type {
        return Element.self
    }
}

[String]().elementType() is OptionalProtocol.Type  // false
[String?]().elementType() is OptionalProtocol.Type // true

Specific to Swift 2, the Array extension can be foregone entirely since the typealias allow to access the Element type:

protocol OptionalProtocol {}

extension Optional : OptionalProtocol {}

[String]().dynamicType.Element.self is OptionalProtocol.Type  // false
[String?]().dynamicType.Element.self is OptionalProtocol.Type // true
Community
  • 1
  • 1
Maic López Sáenz
  • 10,385
  • 4
  • 44
  • 57
0

To do this, I think I need to be able to test whether the Array's type T is an optional type or not.

No, you don't have to - just use flatMap!

let foo: [String?] = [nil, "bar", "baz"]
let foo2: [String] = ["bar", "baz"]

foo.flatMap{$0}
foo2.flatMap{$0}
// yield both ["bar", "baz"] and are of type Array<String>
heiko
  • 1,268
  • 1
  • 12
  • 20