Swift:相同类型的要求使泛型参数等效?
Swift: Same-Type requirement makes generic parameters equivalent?
我正在使用 swift 5 并尝试编译以下代码:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
我遇到编译错误:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
如何让它们等价?或者我不能?
谢谢。
更新 1:
这是我遇到的问题:我想声明结构“AItem”,希望它有一个泛型类型“T”。而且这个泛型类型会有一些限制,比如:“T:StringProtocol”。然后因为某些原因,我需要用一个数组来加载这些结构体,并且保证每个结构体的泛型可以随意设置。
我了解到有“type-erase”可能可以解决这个问题。所以我尝试了这种方式,但似乎没有成功。出现了上面提到的问题。
更新 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
你看,如果你编译这段代码,你会得到一个编译错误:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
如果我使用“var array: [AItem]”,我将无法在数组中放入任何其他非“String”但实现了“StringProtocol”的实例。
这就是为什么我说要“保证每个结构体的泛型可以随意设置”的原因。
更新 3:
非常感谢@jweightman,现在我再次更新我的问题。
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
这是我的全部思路,但都是伪代码
似乎类型擦除是您问题的答案。类型擦除模式的关键思想是将强类型但不兼容的数据(如 AItem<String>
和 AItem<Data>
)放入另一个数据结构中,该数据结构以“不太精确”的类型(通常是 Any
).
类型擦除的一个主要缺点是您正在丢弃类型信息——如果您以后需要恢复它以弄清楚您需要对数组中的每个元素做什么,您需要尝试将您的数据转换为每种可能的类型,这可能是混乱和脆弱的。出于这个原因,我通常尽量避免使用它。
无论如何,这是一个基于您的伪代码的类型擦除示例:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
您可以考虑使用具有关联值的枚举来定义“总和类型”,如果您的泛型可以采用一小部分有限的具体类型,那么您可以考虑使用一种替代类型擦除的方法。如果您感兴趣的协议来自您无法更改的库,那么这可能不是一个好的选择。实际上,总和类型可能如下所示:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}
我正在使用 swift 5 并尝试编译以下代码:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
我遇到编译错误:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
如何让它们等价?或者我不能?
谢谢。
更新 1:
这是我遇到的问题:我想声明结构“AItem”,希望它有一个泛型类型“T”。而且这个泛型类型会有一些限制,比如:“T:StringProtocol”。然后因为某些原因,我需要用一个数组来加载这些结构体,并且保证每个结构体的泛型可以随意设置。
我了解到有“type-erase”可能可以解决这个问题。所以我尝试了这种方式,但似乎没有成功。出现了上面提到的问题。
更新 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
你看,如果你编译这段代码,你会得到一个编译错误:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
如果我使用“var array: [AItem
这就是为什么我说要“保证每个结构体的泛型可以随意设置”的原因。
更新 3:
非常感谢@jweightman,现在我再次更新我的问题。
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
这是我的全部思路,但都是伪代码
似乎类型擦除是您问题的答案。类型擦除模式的关键思想是将强类型但不兼容的数据(如 AItem<String>
和 AItem<Data>
)放入另一个数据结构中,该数据结构以“不太精确”的类型(通常是 Any
).
类型擦除的一个主要缺点是您正在丢弃类型信息——如果您以后需要恢复它以弄清楚您需要对数组中的每个元素做什么,您需要尝试将您的数据转换为每种可能的类型,这可能是混乱和脆弱的。出于这个原因,我通常尽量避免使用它。
无论如何,这是一个基于您的伪代码的类型擦除示例:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
您可以考虑使用具有关联值的枚举来定义“总和类型”,如果您的泛型可以采用一小部分有限的具体类型,那么您可以考虑使用一种替代类型擦除的方法。如果您感兴趣的协议来自您无法更改的库,那么这可能不是一个好的选择。实际上,总和类型可能如下所示:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}