[英]Why can't a generic struct type be inferred by the argument it is initialized with?
我不确定我是否可以足够清楚地描述这个问题,但已经设法制作了一个小(足够)可重现的例子。 在这段代码中,我为来自源 A 的实体和来自源 B 的实体创建了一个具有协议约束扩展的通用结构。调用正确扩展中的方法。
protocol Entity { }
protocol FromSource_A: Entity { }
protocol FromSource_B: Entity { }
struct Apple: FromSource_A { }
struct Orange: FromSource_B { }
protocol StructProtocol {
func go ()
}
struct MyStruct<T: Entity>: StructProtocol {
func go () {
print("MyStruct default go()")
}
}
extension MyStruct where T : FromSource_A {
func go () {
print("MyStruct extension where T : FromSource_A")
}
}
extension MyStruct where T : FromSource_B {
func go () {
print("MyStruct extension where T : FromSource_B")
}
}
let myStruct = MyStruct<Apple>()
myStruct.go() // <- Output: "MyStruct extension where T : FromSource_A"
现在,当我向MyStruct
添加一个也是泛型的属性并使用传递给它的GenericArgument
初始化它的具体实例时,我希望具体类型GenericArgument<Apple>
将告知泛型MyStruct
T
是什么,并允许在MyStruct
上调用正确的扩展名。
....
struct MyStruct<T: Entity>: StructProtocol {
var genericArgument: GenericArgument<T> // Adding generic argument that takes T from MyStruct declaration
func go () {
print("MyStruct default go()")
}
}
extension MyStruct where T : FromSource_A {
func go () {
print("MyStruct extension where T : FromSource_A")
}
}
extension MyStruct where T : FromSource_B {
func go () {
print("MyStruct extension where T : FromSource_B")
}
}
/// Now introduce passing type between structs
struct GenericArgument<T: Entity> { }
func test<T: Entity> (argument: GenericArgument<T>) {
let myStruct = MyStruct<T>(genericArgument: argument)
myStruct.go()
}
let genericArgument = GenericArgument<Apple>()
test(argument: genericArgument) // <- Output: "MyStruct default go()"
而是调用MyStruct
的默认方法实现。
据我所见,当我引入另一个层以通过( func test<T: Entity> (argument: GenericArgument<T>) {}
)传递通用信息时,问题就出现了,在给定具体参数的情况下,接收者可以' t弄清楚它的泛型类型已经填充了什么。
当 MyStruct 的类型由初始化参数推断时,为什么MyStruct
不调用正确扩展中的方法?
您似乎正在尝试从 generics 中创建 class inheritance。 那是不可能的。 Generics 不是动态调度的。 这是故意的,并允许更多的优化。
像您在此处所做的那样,使用where
子句对默认实现提供专门的扩展应该只是为了提高性能。 如果编译器可以证明有关类型的某些信息(例如,它是双向集合而不是序列),那么提供更有效的算法来生成相同的 output 会很有用。 但是对MyStruct.go()
的所有调用都应该具有相同的语义(产生相同的输出)。 仅根据编译时可用的信息在编译时决定调用哪个版本的go
。 可能会从程序的其他部分以不同的类型调用test()
,因此 function 不能专门用于应用正确的where
子句。 它必须假设允许的最一般情况。
在这种特定情况下,如果我添加以下行,您会期望发生什么:
extension Apple: FromSource_B {}
这是完全合法的,因为Apple
符合FromSource_B
。 我什至可以在另一个模块中添加这行代码(在编译完这里的所有内容之后)。 那么应该运行什么代码呢? 这是指向一个设计错误。
与其尝试重新创建类继承覆盖,您可能希望在这里将行为附加到 Entity 类型。 例如:
// Entities have a way to go()
protocol Entity {
static func go()
}
// And if they don't provide one, there's a default
extension Entity {
static func go() {
print("MyStruct default go()")
}
}
// FromSource_A and _B provide their own default ways to conform
protocol FromSource_A: Entity { }
protocol FromSource_B: Entity { }
extension FromSource_A {
static func go() {
print("MyStruct extension where T : FromSource_A")
}
}
extension FromSource_B {
static func go() {
print("MyStruct extension where T : FromSource_B")
}
}
// Apple and Orange conform, and take the default behaviors (they could provide their own)
struct Apple: FromSource_A { }
struct Orange: FromSource_B { }
// MyStruct (there's no need for a protocol) accepts a GenericArgument, but
// only to nail down what `T` is.
struct GenericArgument<T: Entity> { }
struct MyStruct<T: Entity> {
var genericArgument: GenericArgument<T>
func go () {
T.go()
}
}
// And the rest
func test<T: Entity> (argument: GenericArgument<T>) {
let myStruct = MyStruct<T>(genericArgument: argument)
myStruct.go()
}
let genericArgument = GenericArgument<Apple>()
test(argument: genericArgument) // MyStruct extension where T : FromSource_A
你仍然需要小心一点。 在某些情况下它可能会破裂。 例如,如果有人在另一个模块中编写此代码怎么办:
extension Apple {
static func go() { print("This is an Apple.") }
}
这可能会或可能不会像您预期的那样运行。 我会努力摆脱所有 generics 以及几乎所有协议,并使用简单的结构和琐碎的协议来做到这一点:
protocol Entity {}
protocol Source {
func go()
func makeEntity() -> Entity
}
struct Apple: Entity { }
struct Orange: Entity { }
struct Source_A: Source {
func go() { print("From A") }
func makeEntity() -> Entity { return Apple() }
}
struct Source_B: Source {
func go() { print("From B") }
func makeEntity() -> Entity { return Orange() }
}
struct GenericArgument {
let source: Source
}
struct MyStruct {
var genericArgument: GenericArgument
func go () {
genericArgument.source.go()
}
}
func test(argument: GenericArgument) {
let myStruct = MyStruct(genericArgument: argument)
myStruct.go()
}
let genericArgument = GenericArgument(source: Source_A())
test(argument: genericArgument)
您的问题可能实际上需要 generics 在这里,但您应该从尽可能简单地写出代码开始(包括允许它重复),然后寻找如何使用 generics 删除该重复。 你不应该太快跳到 generics; 我们大多数人都会选择错误的抽象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.