当我创建一个依赖于泛型类型的计算属性时,当在泛型函数中传递实例时,特定的实现会“丢失”。
例如,我添加了isBool上Array说回报true,如果Array.Element是Bool:
extension Array {
var isBool: Bool {
false
}
}
extension Array where Element == Bool {
var isBool: Bool {
true
}
}
Run Code Online (Sandbox Code Playgroud)
直接在实例上使用它工作正常
let boolArray: [Bool] = [true, false]
let intArray: [Int] = [1, 0]
boolArray.isBool // true
intArray.isBool // false
Run Code Online (Sandbox Code Playgroud)
但是在泛型函数中,它总是使用非专门化的实现:
func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}
isBool(boolArray) // false, instead of true
isBool(intArray) // false
Run Code Online (Sandbox Code Playgroud)
这不是一个真正的用例,所以我真的不需要一种方法来“修复”这个问题,但我想了解它为什么会这样。
专业化并不能取代继承。它应该用于提高性能,而不是改变行为。
例如,distance(from:to:)通常是 O(k),其中k是距离。对于 RandomAccessCollection,由于特殊化,它可以在 O(1) 中执行。但无论哪种方式,结果都是一样的。
专门化是在编译时根据编译器拥有的信息完成的。在您的示例中,编译器可以看到它boolArray是 a [Bool],因此它使用专门的扩展。但在 的内部isBool,编译器只知道它是array一个数组。它不知道编译函数时会传递什么样的数组。因此它选择更通用的版本来涵盖所有情况。
(出于优化目的,编译器可能会isBool在二进制文件中创建多个版本的 ,但幸运的是,我还没有发现任何情况会影响扩展或重载的调用。即使它实际上创建了内联的、特定于 Bool 的版本isBool,它也会仍然使用更通用的数组扩展。这是一件好事。)
保留您的扩展,以下内容将满足您的期望(尽管我不鼓励这样做):
func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}
func isBool(_ array: [Bool]) -> Bool {
array.isBool
}
Run Code Online (Sandbox Code Playgroud)
现在isBool已超载,将选择最具体的一个。在第二个版本的上下文中,array已知为[Bool],因此将选择更专业的扩展。
尽管上述方法有效,但我强烈建议不要使用专门的扩展或改变行为的模糊重载。它是脆弱且令人困惑的。如果在另一个Element 未知的泛型方法isBool()的上下文中调用,它可能再次无法按您的预期工作。
由于您希望将此基于运行时类型,因此 IMO 您应该在运行时使用 查询类型is。这消除了所有的歧义。例如:
extension Array {
var isBool: Bool { Element.self is Bool.Type }
}
func isBool<Element>(_ array: [Element]) -> Bool {
array.isBool
}
Run Code Online (Sandbox Code Playgroud)
您可以通过添加协议使其更加灵活和强大:
protocol BoolLike {}
extension Array {
var isBool: Bool { Element.self is BoolLike.Type }
}
Run Code Online (Sandbox Code Playgroud)
现在,您想要获得“类似布尔”行为的任何类型只需要符合:
extension Bool: BoolLike {}
Run Code Online (Sandbox Code Playgroud)
这允许您扩展的所有灵活性(即isBool代码不需要知道所有类型),同时确保基于运行时类型而不是编译时类型应用行为。
以防万一,请记住协议本身并不符合协议。所以[BoolLike]才会回来isBool == false。对于带有 的扩展也是如此where Element: BoolLike。如果你需要那种东西才能工作,你需要明确地处理它。
extension Array {
var isBool: Bool {
Element.self is BoolLike.Type || Element.self == BoolLike.self
}
}
Run Code Online (Sandbox Code Playgroud)