Swift 使用依赖于元素类型的递归属性/方法扩展集合
在的上下文中,我思考了如何实现跨集合中所有嵌套级别计数的属性或方法 直觉上,这应该是可行的:Swift 使用依赖于元素类型的递归属性/方法扩展集合,swift,generics,recursion,collections,swift-extensions,Swift,Generics,Recursion,Collections,Swift Extensions,在的上下文中,我思考了如何实现跨集合中所有嵌套级别计数的属性或方法 直觉上,这应该是可行的: extension Collection { var flatCount: Int { if self.count == 0 { return 0 } else if self.first is Collection { // .Iterator.Element: Collection return self.re
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 }
}
}
}
但是,我们不允许将值强制转换为具有关联类型的协议类型
所以我想让元素类型更显式,如下所示:
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
}
}
现在这就编译了——耶!——但调度已关闭:$1。flatCount
不绑定到第二个递归版本,而是始终绑定到第一个普通版本。也就是说,flatCount
只计算第一个嵌套级别
有没有一种方法可以处理类型和/或分派以表达此功能?或者我是以一种(或两种)完全迂回的方式来解决这个问题
旁注:在最后一个示例和第一个函数中,我不使用
self.reduce(0) { $0 + 1 }
因为那不是编译;这里,
$0
是两个匿名参数的对!我认为这是不必要的令人惊讶的行为,并发布到Swift bugtracker上。我认为目前不可能编写这样的递归扩展,其中基本情况由静态类型的一致性决定
尽管请注意,Collection
确实有一个count
属性要求,但它只是一个类型(关联的类型),而不是Int
。因此,如果这是可能的,您可以将其表示为:
extension Collection {
var flatCount: IndexDistance {
return count
}
}
extension Collection where Iterator.Element: Collection {
var flatCount: IndexDistance {
// compiler error: unable to infer closure type in the current context
// (if you expand it out, it will tell you that it's because
// $1.flatCount is ambiguous)
return self.reduce(0) { $0 + $1.flatCount }
}
}
然而,这会产生一个编译器错误(尽管为什么当flatCount
是Int
时它不会发生,但我不知道–它们应该一致编译或不编译)。问题是Swift想要静态地分派$1.flatCount
——这意味着它只能选择一个要调用的扩展名(在这种情况下,编译器认为这两个扩展名同样有效)
静态分派在这里工作的唯一方式是,如果实现专门针对调用它们的每种具体类型的集合
。在这种情况下,由于编译器将知道实现中的具体类型,从而知道是否Iterator.Element.Iterator.Element:Collection
,并相应地进行分派,因此可以解决歧义
然而,目前专业化只是一种优化(因为它有可能在不使用内联来抵消这一额外成本的情况下大幅增加代码大小)——因此不可能保证静态调度在所有情况下都有效
即使$1.flatCount
能够通过例如a(请参见它们)进行动态调度,也需要在运行时根据扩展的类型约束进行重载解析(以便确定要调用的扩展)。然而,Swift在运行时不能解决重载问题(这将是昂贵的)。相反,重载本身是在编译时解决的,动态调度则允许重载的实现相对于它所调用的值是多态的(即它可以将重载的值自己的实现调度到一个函数)
不幸的是,我认为最接近的方法可能是为
Array
编写一个扩展,并使用条件类型转换来迭代嵌套数组:
extension Array {
var flatCount: Int {
var iterator = makeIterator()
if let first = iterator.next() as? [Any] {
// must be an array of arrays – otherwise $1 as! [Any] will crash.
// feel free to add error handling or adding support for heterogeneous arrays
// by doing an O(n) walk.
return iterator.reduce(first.flatCount) { $0 + ($1 as! [Any]).flatCount }
} else {
return count
}
}
}
let arr = [[[[2, 3, 4]], [3, 4, 5, 6]], [57, 89]]
print(arr.flatCount) // 9
尽管请注意,如下面的注释所示,在大多数情况下,将转换为(?/!)[Any]
将创建一个新数组(由于Swift存储具体类型值和抽象类型值的方式不同,请参见),这使得上述实现不是特别有效
一种可能的解决方案是使用“虚拟协议”来声明flatCount
属性:
// dummy protocol to prevent conversions of arrays with concrete-typed elements to [Any].
protocol _Array {
var flatCount: Int { get }
}
extension Array : _Array {
var flatCount: Int {
var iterator = makeIterator()
if let first = iterator.next() as? _Array {
// same comment as above, can crash for heterogeneous arrays.
return iterator.reduce(first.flatCount) { $0 + ($1 as! _Array).flatCount }
} else {
return count
}
}
}
这避免了从具体类型元素数组到抽象类型元素的O(n)转换(相反,只为给定数组创建一个框)
如果我们使用阵列对两种实现(在MacBook Pro上发布)进行粗略的快速基准测试:
let arr = Array(repeating: Array(repeating: Array(repeating: 1, count: 100), count: 100), count: 1000)
对于10次重复调用
flatCount
,第一个分机的时间为31.7秒。应用于第二个实现的相同基准测试产生0.93秒。我认为目前不可能编写这样的递归扩展,其中基本情况由静态类型的一致性决定
尽管请注意,Collection
确实有一个count
属性要求,但它只是一个类型(关联的类型),而不是Int
。因此,如果这是可能的,您可以将其表示为:
extension Collection {
var flatCount: IndexDistance {
return count
}
}
extension Collection where Iterator.Element: Collection {
var flatCount: IndexDistance {
// compiler error: unable to infer closure type in the current context
// (if you expand it out, it will tell you that it's because
// $1.flatCount is ambiguous)
return self.reduce(0) { $0 + $1.flatCount }
}
}
然而,这会产生一个编译器错误(尽管为什么当flatCount
是Int
时它不会发生,但我不知道–它们应该一致编译或不编译)。问题是Swift想要静态地分派$1.flatCount
——这意味着它只能选择一个要调用的扩展名(在这种情况下,编译器认为这两个扩展名同样有效)
静态分派在这里工作的唯一方式是,如果实现专门针对调用它们的每种具体类型的集合
。在这种情况下,由于编译器将知道实现中的具体类型,从而知道是否Iterator.Element.Iterator.Element:Collection
,并相应地进行分派,因此可以解决歧义
然而,目前专业化只是一种优化(因为它有可能在不使用内联来抵消这一额外成本的情况下大幅增加代码大小)——因此不可能保证静态调度在所有情况下都有效
即使$1.flatCount
能够通过例如a(请参见上面的)动态调度,也可以基于co类型的重载解析
extension Collection {
var flatCount: Int {
return CollectionHelper().flatCountH(self)
}
}
fileprivate class CollectionHelper {
func flatCountH<C: Collection>(_ c: C) -> Int where C.Iterator.Element: Collection {
typealias E = C.Iterator.Element
return c.reduce(0) { (res: Int, elem: E) -> Int in
res + self.flatCountH(elem)
}
}
func flatCountH<C: Collection>(_ c: C) -> Int {
return c.reduce(0) { (count, _) in count + 1 }
}
}