为什么泛型专业化会在泛型函数中丢失 [英] Why is generic specialization lost inside a generic function

查看:63
本文介绍了为什么泛型专业化会在泛型函数中丢失的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我创建一个依赖于泛型类型的计算属性时,特定实现将丢失".当实例通过通用函数传递时.

When I create a computed property that depends on a generic type, the specific implementation is "lost" when the instance is passed in a generic function.

例如,我在 Array 上添加了 isBool ,如果 Array.Element true >布尔:

For example, I added the isBool on Array that returns true if Array.Element is Bool:

extension Array {
    var isBool: Bool {
        false
    }
}

extension Array where Element == Bool {
    var isBool: Bool {
        true
    }
}

直接在实例上使用它会很好

Using it directly on the instance works fine

let boolArray: [Bool] = [true, false]
let intArray: [Int] = [1, 0]

boolArray.isBool // true
intArray.isBool // false

但是在泛型函数中,它始终使用非专用实现:

But inside a generic function it always uses the non specialized implementation:

func isBool<Element>(_ array: [Element]) -> Bool {
    array.isBool
}

isBool(boolArray) // false, instead of true
isBool(intArray) // false

这不是真正的用例,因此我真的不需要修复"程序.这样,但我想了解为什么会这样.

This is not a real use case so I don't really need a way to "fix" this, but I would like to understand why it behave like that.

推荐答案

专业化不能替代继承.应该使用它来提高性能,而不是改变行为.

Specialization is not a replacement for inheritance. It should be used to improve performance, not change behavior.

例如, distance(from:to:)通常为O(k),其中 k 是距离.对于RandomAccessCollection,由于具有特殊性,可以在O(1)中执行.但是两种方法的结果都是相同的.

For example, distance(from:to:) is usually O(k), where k is the distance. For RandomAccessCollection it can be performed in O(1) due to a specialization. But the result is the same either way.

专业化是在编译时根据编译器具有的信息进行的.在您的示例中,编译器可以看到 boolArray [Bool] ,因此它使用了专门的扩展名.但是在 isBool 内部,编译器只知道 array 是一个Array.它不知道何时编译函数将传递哪种类型的数组.因此,它选择了更通用的版本来涵盖所有情况.

Specialization is done at compile-time based on the information the compiler has. In your example, the compiler can see that boolArray is a [Bool], and so it uses the specialized extension. But inside of isBool, all that the compiler knows is that array is an Array. It doesn't know when compiling the function what kind of Array will be passed. So it picks the more general version to cover all cases.

(出于优化目的,编译器可能会在二进制文件中创建 isBool 的多个版本,但幸运的是,我没有发现任何会影响扩展或重载调用的情况.即使它实际上创建了是一个内联的,特定于Bool的 isBool 版本,它仍将使用更通用的Array扩展.这是一件好事.)

(The compiler may create multiple versions of isBool in the binary for optimization purposes, but luckily I haven't found any situations where this impacts what extensions or overloads are called. Even if it actually creates an inlined, Bool-specific version of isBool, it will still use the more general Array extension. That's a good thing.)

将扩展保留在原位,以下将达到您的期望(尽管我不鼓励这样做):

Leaving your extensions in place, the following would do what you expect (though I don't encourage this):

func isBool<Element>(_ array: [Element]) -> Bool {
    array.isBool
}

func isBool(_ array: [Bool]) -> Bool {
    array.isBool
}

现在 isBool 已超载,将选择最具体的一个.在第二个版本的上下文中,已知 array [Bool] ,因此将选择更专业的扩展名.

Now isBool is overloaded and the most specific one will be selected. Within the context of the second version, array is known to be [Bool], and so the more specialized extension will be selected.

即使以上方法都能奏效,但我还是强烈建议您不要使用更改行为的专用扩展名或含糊不清的重载.这是脆弱而令人困惑的.如果另一个通用方法的上下文中调用了 isBool(),而Element未知,则它可能再次无法按预期工作.

Even though the above works, I would strongly recommend against using specialized extensions or ambiguous overloads that change behavior. It is fragile and confusing. If isBool() is called in the context of another generic method where Element is not known, it again may not work as you expect.

由于要基于运行时类型,因此IMO应该在运行时使用 is 查询类型.这消除了所有歧义.例如:

Since you want to base this on the runtime types, IMO you should query the type at runtime using is. That gets rid of all the ambiguity. For example:

extension Array {
    var isBool: Bool { Element.self is Bool.Type }
}

func isBool<Element>(_ array: [Element]) -> Bool {
    array.isBool
}

您可以通过添加协议来使其更加灵活和强大:

You can make this much more flexible and powerful by adding a protocol:

protocol BoolLike {}
extension Array {
    var isBool: Bool { Element.self is BoolLike.Type }
}

现在,您想要获得类似bool"的任何类型的商品,行为只需符合以下条件即可:

Now, any types you want to get "bool-like" behavior just need to conform:

extension Bool: BoolLike {}

这为您提供了扩展的所有灵活性(即, isBool 代码不需要知道所有类型),同时确保行为是基于运行时类型而不是编译时应用的类型.

This allows you all the flexibility of your extensions (i.e. the isBool code doesn't need to know all the types), while ensuring the behavior is applied based on runtime types rather than compile-time types.

以防万一,请记住协议不符合自己.因此, [BoolLike] 将返回 isBool == false .具有 where元素:BoolLike 的扩展名也是如此.如果您需要这种方法来工作,则需要对其进行明确处理.

Just in case it comes up, remember that protocols do not conform to themselves. So [BoolLike] would return isBool == false. The same is true for an extension with where Element: BoolLike. If you need that kind of thing to work, you need to deal with it explicitly.

extension Array {
    var isBool: Bool {
        Element.self is BoolLike.Type || Element.self == BoolLike.self
    }
}

这篇关于为什么泛型专业化会在泛型函数中丢失的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆