简体   繁体   English

Swift:同类型要求使泛型参数等效?

[英]Swift: Same-Type requirement makes generic parameters equivalent?

I'm using swift 5 and try to compile the following code:我正在使用 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
    }
}

I got compilation error:我得到编译错误:

error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
    init<G: StringProtocol>(str: G) where G == T {
                                            ^

How to make them equivalent?如何使它们等效? or I can't?还是我不能?

Thanks.谢谢。


Update 1:更新1:

This is the problem I encountered: I want to declare struct "AItem", hoping it has a generic type "T".这是我遇到的问题:我想声明结构“AItem”,希望它有一个泛型类型“T”。 And this generic type will have some restrictions, such as: "T: StringProtocol".而且这个泛型类型会有一些限制,比如:“T:StringProtocol”。 Then for some reason, I need to use an array to load these structs, and ensure that the generics of each structure can be set at will.然后由于某种原因,我需要使用一个数组来加载这些结构体,并确保每个结构体的generics可以随意设置。

I learned that there is "type-erase" might can solve this.我了解到有“类型擦除”可能可以解决这个问题。 So I tried this way, but it seemed unsuccessful.所以我尝试了这种方式,但似乎没有成功。 The problems mentioned above have occurred.出现了上述问题。


Update 2:更新 2:

struct AItem<T: StringProtocol> {
    var aStr: T
}

var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]

Look,If you compile this code, you will get a compilation error:看,如果你编译这段代码,你会得到一个编译错误:

error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
            ^

If I use "var array: [AItem<String>]", I will not be able to put any other non-"String" but implemented "StringProtocol" instance in the array.如果我使用“var array: [AItem<String>]”,我将无法在数组中放置任何其他非“String”但实现了“StringProtocol”实例。

This is why I said I want "ensure that the generics of each structure can be set at will".这就是为什么我说我要“确保每个结构的generics可以随意设置”。


Update 3:更新 3:

very thanks for @jweightman, now I update my question again.非常感谢@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
}

This is my whole idea, but all of which are pseudo-code.这是我的全部想法,但所有这些都是伪代码。

It seems like type erasure is the answer to your problem.似乎类型擦除是您问题的答案。 The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data> ) inside of another data structure which stores them with "less precise" types (usually Any ).类型擦除模式的关键思想是将强类型但不兼容的数据(如AItem<String>AItem<Data> )放在另一个数据结构中,该数据结构以“不太精确”的类型(通常是Any )存储它们。

A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle.类型擦除的一个主要缺点是你丢弃了类型信息——如果你以后需要恢复它以弄清楚你需要对数组中的每个元素做什么,你需要尝试将你的数据转换为每个元素可能的类型,可能是混乱和脆弱的。 For this reason, I've generally tried to avoid it where possible.出于这个原因,我通常会尽可能地避免它。

Anyways, here's an example of type erasure based on your pseudo code:无论如何,这是基于您的伪代码的类型擦除示例:

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"
}

An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type".您可以考虑使用类型擦除的替代方法,如果您的泛型可以采用一小部分有限的具体类型,这种方法效果很好,那就是使用具有关联值的枚举来定义“总和类型”。 This might not be a good choice if the protocol you're interested in is from a library that you can't change.如果您感兴趣的协议来自您无法更改的库,这可能不是一个好的选择。 In practice, the sum type might look like this:在实践中,sum 类型可能如下所示:

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"
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM