Reputation: 360
I'm creating a protocol to abstract different models (hand-crafted, decodable, and thrift-generated) like so:
protocol FeedItemModeling {
var text: String? { get }
var url: URL? { get }
}
protocol FeedModeling {
var items: [FeedItemModeling]? { get }
}
This would work great except that thrift models aren't using a basic array, but a TList
which conforms to RandomAccessCollection
among other things. Since both Array
and TList
conform to RandomAccessCollection
, I'm trying to change the protocol definition to:
Attempt A)
var items: RandomAccessCollection<FeedItemModeling>? { get }
which gives the error: "Cannot specialize non-generic type 'RandomAccessCollection'." I assume it's because associatedtype
is a half-baked generics in the sense that it can enforce constraints internally in the protocol itself, but isn't exposed externally; meaning that in our case, RandomAccessCollection
's associatedtypes
are resolved within RandomAccessCollection
but there's no way for FeedModeling
to access it?
Attempt B)
var items: RandomAccessCollection? { get }
gives "Protocol 'RandomAccessCollection' can only be used as a generic constraint because it has Self or associated type requirements" and loses the constraint on FeedItemModeling anyway.
Attempt C)
associatedtype C: RandomAccessCollection
var items: C? { get }
works, but we lost the constraint on FeedItemModeling
, so that's not acceptable.
Attempt D)
associatedtype C: RandomAccessCollection
var items: C<FeedItemModeling>? { get }
gives "Cannot specialize non-generic type 'Self.C'." I assume it's for the same reason as above?
Attempt E)
associatedtype C<E>: RandomAccessCollection where E: FeedItemModeling
var items: C? { get }
gives "Associated types must not have a generic parameter list". I assume it's because now there's a constraint on a constraint, which can't be expressed this way?
Attempt F)
typealias C<E> = RandomAccessCollection
var items: C<FeedItemModeling>? { get }
gives: "Protocol 'RandomAccessCollection' can only be used as a generic constraint because it has Self or associated type requirements". Same reason?
Attempt G)
associatedtype FeedItemModelingList = RandomAccessCollection where RandomAccessCollection.Element: FeedItemModeling
var items: FeedItemModelingList? { get }
gives "Associated type 'Element' can only be used with a concrete type or generic parameter base". I don't understand this one; please let me know why?
Attempt H)
protocol FeedItemModelingList: RandomAccessCollection {}
...
var items: FeedItemModelingList? { get }
gives: "Protocol 'FeedItemModelingList' can only be used as a generic constraint because it has Self or associated type requirements".
Attempt I)
protocol FeedItemModelingList: RandomAccessCollection where Element == FeedItemModeling {}
...
associatedtype L = FeedItemModelingList
var items: L? { get }
seems to work (so far), but is really ugly.
Any better idea?
Upvotes: 0
Views: 706
Reputation: 8963
I think what you want is
protocol FeedItemModeling {
var text: String? { get }
var url: URL? { get }
}
protocol FeedModeling {
associatedtype TList: RandomAccessCollection where TList.Element == FeedItemModeling
var items: TList? { get }
}
As for your questions:
I assume it's because associatedtype is a half-baked generics
I think in modern Swift, associatedtype is pretty close to parity with generics, although both are somewhat incomplete, particularly around existentials, although it gets better each year. One rule of thumb is that a lot of Swift generic/PAT problems are solved with where
.
gives "Cannot specialize non-generic type 'Self.C'." I assume it's for the same reason as above?
IMO this error is quite poor, but it's basically a syntax issue about the <>
, which is allowed only for generic types and not protocols. "Specialization" is a bit of an overloaded term in the Swift community and can mean anything from a type constraint to emitting machine code for a known type parameter value.
gives "Associated types must not have a generic parameter list". I assume it's because now there's a constraint on a constraint, which can't be expressed this way?
You can express a constraint on a constraint, but there isn't rust-like <F=A>
syntax yet. Currently, you need where
can only be used as a generic constraint because it has Self or associated type requirements".
This is the infamous "PAT" or "generalized existentials" problem (see the Generics Manifesto). Everybody's annoyed about it, hopefully one day we'll support it.
"Associated type 'Element' can only be used with a concrete type or generic parameter base". I don't understand this one; please let me know why?
RandomAccessCollection
is just some protocol, that you happen to have be using nearby but Swift doesn't know the connection. So when you say this
associatedtype FeedItemModelingList = RandomAccessCollection where RandomAccessCollection.Element: FeedItemModeling
it's a bit like you did
func foo() where Collection.Element == String
Collection.Element
is 'closed' in this context, there's no way to specialize it or give it a constraint. Instead, you constrain something which is open, such as the associatedtype you're declaring.
Upvotes: 0