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.
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!")
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.
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?
Swift doesn't allow comparing Optional<Any> to Any, so the last line doesn't work.
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.
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:
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.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With