简体   繁体   中英

swift: AnyCollection with different generic types

AnyCollection([1, 2, 3]) // AnyCollection<Int>
AnyCollection(["a", "b", "c"]) // AnyCollection<String>

These two AnyCollection is great, but they have different Generic types, namely Int and String .

But I can still add these two AnyCollection to a single array, like this

// [AnyCollection<Any>]
let array = [AnyCollection([1, 2, 3]), AnyCollection(["a", "b", "c"])] 

I can't understand what happened to these two AnyCollection .

Why can it convert from String or Int to Any ??

I wrote some code to create one myself.

// ❌ Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
// Contains AnyMyCollection<String> && AnyMyCollection<Int> at a single collection.
var collections = [AnyMyCollection(Sports()), AnyMyCollection(Animals()), AnyMyCollection(Digits())]

protocol MyCollection<Element> {
    associatedtype Element
    func allValues() -> [Element]
}

// MARK: - AnyMyCollection

struct AnyMyCollection<Element> {
    internal var _box: _AnyMyCollectionBase<Element>
    
    init<C: MyCollection>(_ base: C) where C.Element == Element {
        self._box = _MyCollectionBox(base)
    }
}

extension AnyMyCollection: MyCollection {
    func allValues() -> [Element] {
        _box.allValues()
    }
}

final class _MyCollectionBox<Base: MyCollection>: _AnyMyCollectionBase<Base.Element> {
    init(_ base: Base) {
        _base = base
    }
    
    private var _base: Base
    override func allValues() -> [Base.Element] {
        _base.allValues()
    }
}

class _AnyMyCollectionBase<Element>: MyCollection {
    func allValues() -> [Element] {
        return []
    }
}

// MARK: - Diffrent Types of My Collection

struct Animals: MyCollection {
    typealias Element = String
    func allValues() -> [Element] {
        ["Monkey", "Tiger", "Lion"]
    }
}

struct Sports: MyCollection {
    typealias Element = String
    func allValues() -> [Element] {
        ["Basketball", "Football", "Baseball"]
    }
}

struct Digits: MyCollection {
    typealias Element = Int
    func allValues() -> [Element] {
        [1, 2, 3, 4, 5]
    }
}

I tried to follow the same technique but failed because the type of the element in AnyMyCollection is not the same.

The simple explanation is that you are not Apple.

Generics in general are not covariant over the parameterized type, and you have no way to create to generic that is covariant over the paramterized type. But Apple does.

To see this more simply, consider Array. It is a generic. If you have two classes, a class and its subclass, you can combine arrays of each of them:

    class MyClass {}
    class MySubclass: MyClass {}

    let arr1 = [MyClass()]
    let arr2 = [MySubclass()]

    let arr3 = [arr1, arr2] // [[MyClass]]

The compiler accepts this; it treats the resulting combination as an array of arrays of the superclass, Array<Array<MyClass>> . So for Apple, an Array<MySubclass> is treated as a sort of subclass of Array<MyClass> .

But now try to do that with your own generic. You can't do it:

    class MyGeneric<T> {}

    let g1 = MyGeneric<MyClass>()
    let g2 = MyGeneric<MySubclass>()

    let arr4 = [g1, g2] // error

The compiler does not magically see this as an Array<MyGeneric<MyClass>> . That's because your generic is not covariant.

There are always language proposals sitting around, hoping to allow us ordinary human beings to make covariant generics. But so far, none of them has arrived into the language.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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