Estendendo a coleção com uma propriedade / método recursivo que depende do tipo de elemento
Dentro do contexto deessa questão, Pensei em como implementar uma propriedade ou método que contatudo níveis de aninhamento em coleções.
Intuitivamente, algo que deve 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 }
}
}
}
No entanto, não temos permissão para converter valores em tipos de protocolo que possuem tipos associados.
Então, pensei em tornar o tipo de elemento mais explícito, assim:
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
}
}
Mas isso aparentemente está pedindo muito do tipo inferrador.
Agora dei um passo atrás e decidi parar de tentar colocar tudo em uma extensão:
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 }
}
}
Agora issocompila -- yay! - mas a expedição está desativada:$1.flatCount
não se liga à segunda versão recursiva, mas sempre à primeira versão simples. Isso é,flatCount
conta apenas o primeiro nível de aninhamento.
Existe uma maneira de manipular tipos e / ou despachar de maneira a expressar essa função? Ou vou fazer isso de uma maneira completamente indireta (ou duas)?
Nota lateral: no último exemplo e na primeira função, eu não uso
self.reduce(0) { $0 + 1 }
porque isso não compila; aqui,$0
é opar de ambos os parâmetros anônimos! Eu acho que esse é um comportamento desnecessariamente surpreendente e publicadouma solicitação de mudança para o bugtracker Swift.