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
.