Colección extensible con una propiedad / método recursivo que depende del tipo de elemento
En el contexto deesta pregunta, Pensé en cómo se podría implementar una propiedad o método que cuentetodas Niveles de anidamiento en colecciones.
Intuitivamente, algo que debería funcionar:
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 }
}
}
}
Sin embargo, no podemos emitir valores a tipos de protocolo que tengan tipos asociados.
Entonces pensé hacer que el tipo de elemento sea más explícito, así:
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
}
}
Pero esto aparentemente está pidiendo demasiado del tipo inferidor.
Ahora di un paso atrás y decidí dejar de tratar de combinar todo en una extensión:
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 }
}
}
Ahora estocompila -- ¡Hurra! - pero el envío está apagado:$1.flatCount
no se une a la segunda versión recursiva, sino siempre a la primera versión simple. Es decir,flatCount
solo cuenta el primer nivel de anidamiento.
¿Hay alguna manera de hacer malabarismos con los tipos y / o el envío de una manera de expresar esta función? ¿O lo estoy haciendo de una manera completamente indirecta (o dos)?
Nota al margen: en el último ejemplo y en la primera función, no uso
self.reduce(0) { $0 + 1 }
porque eso no compila; aquí,$0
es elpar de ambos parámetros anónimos! Creo que este es un comportamiento innecesariamente sorprendente y publicadouna solicitud de cambio al rastreador de errores Swift.