In the context of this question, I though about how one could implement a property or method that counts across all nesting levels in collections.
Intuitively, something this should work:
extension Collection {
var flatCount: Int {
if self.count == 0 {
return 0
} else if self.first is Collection { // .Iterator.Element: Collection
return self.reduce(0) { (res, elem) -> Int in
res + (elem as! Collection).flatCount // ERROR
}
} else {
return self.reduce(0) { (res,_) in res + 1 }
}
}
}
However, we are not allowed to cast values to protocol types that have associated types.
So I thought to make the Element type more explicit, like so:
extension Collection {
var flatCount: Int {
return Self.flatCountH(self)
}
private static final func
flatCountH<C: Collection, D>(_ c: C) -> Int
where Iterator.Element == D, D: Collection {
return c.reduce(0) { (res: Int, elem: D) -> Int in
(res + elem.flatCount) as Int // Ambiguous type
}
}
private static final func flatCountH<C: Collection>(_ c: C) -> Int {
return c.reduce(0) { $0 + $1.flatCount } // Unable to infer closure type
}
}
But this is apparently asking too much of the type inferrer.
Now I took a step back and decided to stop trying to wrench everything into one extension:
extension Collection {
var flatCount: Int {
// There's no count on Collection, so...
return self.reduce(0) { (res,_) in res + 1 }
}
}
extension Collection where Iterator.Element: Collection {
var flatCount: Int {
return self.reduce(0) { $0 + $1.flatCount }
}
}
Now this compiles -- yay! -- but the dispatching is off: $1.flatCount
does not bind to the second, recursive version, but always to the first, plain version. That is, flatCount
only counts the first nesting level.
Is there a way to juggle types and/or dispatching in a way to express this function? Or am I going about it in a completely roundabout way (or two)?
Side note: in the last example and in the first function, I don't use
self.reduce(0) { $0 + 1 }
because that does not compile; here, $0
is the pair of both anonymous parameters! I think that this is needlessly surprising behaviour and posted a change request to the Swift bugtracker.