How to use associatedType protocol in vars or arrays throught `some` in Swift 5.1?

I tried to make array or var of ProtocolA but I ran into some errors.
What is going on here?

protocol ProtocolA {
    associatedtype ProtocolAType
    
    var prop1: ProtocolAType { get set }
    
    func description()
    func methodA(param1: ProtocolAType) -> ProtocolAType
}

extension ProtocolA {
    func description() {
        print(self)
    }
    
    func methodA(param1: ProtocolAType) -> ProtocolAType {
        param1
    }
}

struct StructA: ProtocolA {
    var prop1: Int
}

struct StructB: ProtocolA {
    var prop1: String
}

var item1: some ProtocolA = StructA(prop1: 1)
var item2: some ProtocolA = StructB(prop1: "1")

item1.description()


//Cannot invoke 'methodA' with an argument list of type '(param1: Int)'
var result1 = item1.methodA(param1: 1)
//Cannot invoke 'methodA' with an argument list of type '(param1: String)'
var result2 = item1.methodA(param1: "1")


//Cannot convert value of type '[Any]' to specified type 'some ProtocolA'
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
var items1: some ProtocolA = [StructA(prop1: 1), StructB(prop1: "1")]
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
//Return type of var 'items2' requires that '[StructA]' conform to 'ProtocolA'
var items2: some ProtocolA = [StructA(prop1: 1), StructA(prop1: 1)]


for item in items2 {
    item.method(2)
}

I created CustomCollection but it can't work with associatedtype

struct CustomCollection<T> {
    typealias Items = [T]

    private var items: Items

    init(items: Items) {
        self.items = items
    }
}

extension CustomCollection: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: T...) {
        self.items = elements
    }
}

extension CustomCollection: Collection {
    typealias Index = Items.Index
    typealias Element = Items.Element

    var startIndex: Index { items.startIndex }
    var endIndex: Index { items.endIndex }

    subscript(index: Index) -> Iterator.Element {
        get { items[index] }
        set { items[index] = newValue }
    }

    func index(after i: Index) -> Index {
        items.index(after: i)
    }
}

I mist how to specify the associatedtype

//Protocol 'ProtocolA' can only be used as a generic constraint because it has Self or associated type requirements
var items4: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
//An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
var items5: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]

I updated my questions for more clear my issues.

I tried to make array or var comfort to ProtocolA but I ran into some errors.

What is going on here?

I created two protocols with/without associatedtype and made two struct conform to ProtocolA and ProtocolB

protocol ProtocolA {
    associatedtype ProtocolAType
    
    var prop1: ProtocolAType { get set }
    
    func description()
    func methodA(param1: ProtocolAType) -> ProtocolAType
}

protocol ProtocolB {
    func description()
}

extension ProtocolA {
    func description() {
        print(self)
    }
    
    func methodA(param1: ProtocolAType) -> ProtocolAType {
        param1
    }
}

struct StructA: ProtocolA, ProtocolB {
    var prop1: Int
}

struct StructB: ProtocolA, ProtocolB {
    var prop1: String
}

I created CustomCollection to pass some type.

struct CustomCollection<T> {
    typealias Items = [T]

    private var items: Items

    init(items: Items) {
        self.items = items
    }
}

extension CustomCollection: ExpressibleByArrayLiteral {
    init(arrayLiteral elements: T...) {
        self.items = elements
    }
}

extension CustomCollection: Collection {
    typealias Index = Items.Index
    typealias Element = Items.Element

    var startIndex: Index { items.startIndex }
    var endIndex: Index { items.endIndex }

    subscript(index: Index) -> Iterator.Element {
        get { items[index] }
        set { items[index] = newValue }
    }

    func index(after i: Index) -> Index {
        items.index(after: i)
    }
}

CustomCollection works ok with non-associated type protocols.

var items: CustomCollection<ProtocolB> = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
    item.description()
}

I tried to call methodA but I got errors below.

var item1: some ProtocolA = StructA(prop1: 1)
var item2: some ProtocolA = StructB(prop1: "1")

item1.description()

//Cannot invoke 'methodA' with an argument list of type '(param1: Int)'
var result1 = item1.methodA(param1: 1)
//Cannot invoke 'methodA' with an argument list of type '(param1: String)'
var result2 = item2.methodA(param1: "1")

I don't know to make [ProtocolA]

//Cannot convert value of type '[Any]' to specified type 'some ProtocolA'
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
var items1: some ProtocolA = [StructA(prop1: 1), StructB(prop1: "1")]
//Property declares an opaque return type, but cannot infer the underlying type from its initializer expression
//Return type of var 'items2' requires that '[StructA]' conform to 'ProtocolA'
var items2: some ProtocolA = [StructA(prop1: 1), StructA(prop1: 1)]



I'd like to call methodA.

for item in items1 {
    item.methodA(2)
}
for item in items2 {
    item.methodA("2")
}

I mist how to specify the associatedtype

//Protocol 'ProtocolA' can only be used as a generic constraint because it has Self or associated type requirements
var items4: CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]
//An 'opaque' type must specify only 'Any', 'AnyObject', protocols, and/or a base class
var items5: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructB(prop1: "1")]

I wouldn't like to use casting at the call site, like below

var items: some Collection = [StructA(prop1: 1), StructB(prop1: "1")]
for item in items {
    if let item = item as? StructA {
        item.methodA(param1: 4)
    }
    if let item = item as? StructB {
        item.methodA(param1: "3")
    }
}

I'd like to use something like below

var items: some CustomCollection<ProtocolA> = [StructA(prop1: 1), StructA(prop1: 2)]
for item in items {
    item.methodA(param1: 4)
}

I suppose I will have to make different protocols for all supported types without associated types. This one way or has another's?

extension ProtocolA {
    func description()
    func methodA(param1: ProtocolAType) -> ProtocolAType
}

This is peculiar. It is not a valid protocol.


If you mean that parameter and return type of methodA is dictated by the caller, then it is this

protocol ProtocolA {
  ...
  func methodA<T>(param1: T) -> T
}

which you can already put in a collection.


If you mean that the parameter and return type of methodA is dictated by the conformer?

extension ProtocolA {
  associatedtype ProtocolAType

  func description()
  func methodA(param1: ProtocolAType) -> ProtocolAType
}

This is a bona fide Protocol with Associated Type (PAT).

Currently, any PATs requires you to use the same concrete type. Because, allowing things like CustomCollection<ProtocolA> would also allow you to use instances conforming to ProtocolA with different associate types

let collection: CustomCollection<ProtocolA> = [StructA(), StructB()]

It is problematic because things like

for item in collection {
    item.methodA(2)
}

already can't work. We don't know the type of ProtocolAType at collection[0]. It can be Int. It can also be something else.

There seems to be pitches here and there about improving this like adding syntax to specify the associated type, lifting the constraints and disallowing anything that use associated type, etc.

As is, you can only use concrete types for PAT, not a boxing like normal protocol. So you can use concrete type.

var a: CustomCollection<StructA> = [ ... ]
var b: CustomCollection = [ StructA(...), StructA(...) ] // Let the type be inferred to `StructA`

and if you need to pass it around, you can use generic.

func foo<T>(value: CustomCollection<T>) where T: ProtocolA, T.ProtocolAType == String { ... }

PS

You can edit the post directly rather than adding the edited version beneath. It's probably easier to read that way.

Terms of Service

Privacy Policy

Cookie Policy