Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine if a generic is an optional in Swift?

Tags:

I want to extend an Array with a function that would return a count of all non-nil items in an Array. Ideally this would work with an array of any optional or non-optional types. I tried a variety of things that failed to compile, crashed Xcode or both. I would have assumed it would look something like this:

extension Array {     func realCount() -> Int {         var cnt = 0         for value in self {             if value != nil {                 cnt++             }         }          return cnt     } } 

Here Swift complains that T is not convertible to UInt8. Or sometimes MirrorDisposition or other random classes.

So assuming it's possible, what's the trick?

Edit: as of Xcode 6 beta 5 this now compiles but does not give the expected results. if value != nil evaluates true every time.

like image 799
Casey Fleser Avatar asked Jul 31 '14 16:07

Casey Fleser


People also ask

How do you indicate that this variable is optional Swift?

If you defined a variable as optional, then to get the value from this variable, you will have to unwrap it. This just means putting an exclamation mark at the end of the variable. Optional("Hello, Swift 4!")

How do I know if an optional is nil Swift?

You can use if statement and compare optional with nil to find out whether a optional contains a value or not. You can use the comparison operator "equal to" operator ( == ) or the "not equal to" operator ( != ) in the if statement.

What is an optional string in Swift?

An optional in Swift is basically a constant or variable that can hold a value OR no value. The value can or cannot be nil. It is denoted by appending a “?” after the type declaration. For example: var tweet: String?

Can any be optional Swift?

Swift doesn't allow comparing Optional<Any> to Any, so the last line doesn't work.


2 Answers

You can't compare an arbitrary value to nil (EDIT: but see Sulthan's comment below; it may be that we should be able to compare arbitrary values to nil; the rest of this paragraph may be true today, but only due to a compiler bug). While Optional has some bits of syntactic sugar applied to it, it's really just a enum, and nil is just Optional.None. You want one behavior for one type (Optional) and another behavior for all other types. Swift has that via generics, just not in extensions. You have to turn it around into a function:

func realCount<T>(x: [T?]) -> Int {   return countElements(filter(x, { $0.getLogicValue() } ) ) }  func realCount<T>(x: [T]) -> Int {   return countElements(x) }  let l = [1,2,3] let lop:[Int?] = [1, nil, 2]  let countL = realCount(l) // 3 let countLop = realCount(lop) // 2 

This approach is much more flexible. Optional is just one of many types you would want to flatMap this way (for example, you could use this same technique to handle Result).


EDIT: You can take this further by creating a protocol for things you consider "real." That way you don't have to confine this to Optionals. For example:

protocol Realizable {   func isReal() -> Bool }  extension Optional: Realizable {   func isReal() -> Bool { return self.getLogicValue() } }  func countReal<S:Collection>(x: S) -> S.IndexType.DistanceType {   return countElements(x) }  func countReal<S:Collection where S.GeneratorType.Element:Realizable>(x: S) -> Int {   return countElements(filter(x, {$0.isReal()})) } 

This says, if I pass a collection of "realizable" things, then filter them against their rule. Otherwise, just count them. While I probably wouldn't really use this function (it seems very special-case), the concept is useful. Later callers can add new "realizable" types without modifying any of your code (or even knowing how they're implemented). And this shows how to have a default behavior for things that don't implement your protocol.

BTW, I'm using Collections here just because they're easier to count (and I'm being a bit sloppy about the return types; notice one is the DistanceType and the other is an Int). Getting the types right on generic Collection-based functions is still kind of tricky (and often crashes the compiler). I suspect this will all improve in the next betas.

like image 159
Rob Napier Avatar answered Oct 26 '22 08:10

Rob Napier


TL;DR

By using a protocol, you can extend SequenceType to count the number of non-nils.

let array: [Int?] = [1, nil, 3] assert(array.realCount == 2) 

If you just want the code, scroll down to "Solution" below.


I needed to do something similar to create an array.removeNils() extension method.

The problem is that when you try to do something like:

extension SequenceType where Generator.Element == Optional { } 

you get:

error: reference to generic type 'Optional' requires arguments in <...> extension SequenceType where Generator.Element == Optional {                                                   ^ generic type 'Optional' declared here 

So the question is, what type should we add inside the <>? It can't be a hard-coded type since we want it to work for anything, so, instead, we want a generic like T.

error: use of undeclared type 'T' extension SequenceType where Generator.Element == Optional<T> {                                                            ^ 

Looks like there's no way to do this. However, with the help of protocols, you can actually do what you want:

protocol OptionalType { }  extension Optional: OptionalType {}  extension SequenceType where Generator.Element: OptionalType {   func realCount() -> Int {     // ...   } } 

Now it'll only work on arrays with optionals:

([1, 2] as! [Int]).realCount() // syntax error: type 'Int' does not conform to protocol 'OptionalType' ([1, nil, 3] as! [Int?]).realCount() 

The final piece of the puzzle is to compare the elements to nil. We need to extend the OptionalType protocol to allow us to check if an item is nil or not. Sure we could create a isNil() method, but not adding anything to Optional would be ideal. Fortunately, it already has a map function that can help us.

Here's an example of what the map and flatMap functions look like:

extension Optional {   func map2<U>(@noescape f: (Wrapped) -> U) -> U? {     if let s = self {       return f(s)     }     return nil   }    func flatMap2<U>(@noescape f: (Wrapped) -> U?) -> U? {     if let s = self {       return f(s)     }     return nil   } } 

Notice how map2 (an equivalent of the map function) only returns f(s) if self != nil. We don't really care what value returns so we can actually make it return true for clarity. To make the function easier to understand, I'm adding explicit types for each of the variables:

protocol OptionalType {   associatedtype Wrapped   @warn_unused_result   func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U? }  extension Optional: OptionalType {}  extension SequenceType where Generator.Element: OptionalType {   func realCount() -> Int {     var count = 0     for element: Generator.Element in self {       let optionalElement: Bool? = element.map {         (input: Self.Generator.Element.Wrapped) in         return true       }       if optionalElement != nil {         count += 1       }     }     return count   } } 

To clarify, these are what the generic types are mapping to:

  • OptionalType.Wrapped == Int
  • SequenceType.Generator.Element == Optional
  • SequenceType.Generator.Element.Wrapped == Int
  • map.U == Bool

Of course, realCount can be implemented without all those explicit types, and by using $0 instead of true it prevents us from needing to specify _ in in the map function.


Solution

protocol OptionalType {   associatedtype Wrapped   @warn_unused_result   func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? }  extension Optional: OptionalType {}  extension SequenceType where Generator.Element: OptionalType {   func realCount() -> Int {     return filter { $0.map { $0 } != nil }.count   } }  // usage: assert(([1, nil, 3] as! [Int?]).realCount() == 2) 

The key thing to note is that $0 is a Generator.Element (i.e. OptionalType) and $0.map { $0 } converts it to a Generator.Element.Wrapped? (e.g. Int?). Generator.Element or even OptionalType cannot be compared to nil, but Generator.Element.Wrapped? can be compared to nil.

like image 45
Senseful Avatar answered Oct 26 '22 06:10

Senseful