Reputation: 2073
* Short version *
How can I conform a class (extension) to a generic protocol function?
* Long version *
This is a small part of a data structure to support a paginated collection,
protocol Pageable {
//an object whose can be in a collection
}
protocol Page{ //a page of a collection that can be paginated
associatedtype PageItemType
func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType
}
//Bonus question
//class PagedCollection<PageType:Page, ItemType:Pageable> {
//...
//}
Here is the implementation of the protocols with a "real" case:
class Person : Pageable{}
class People {
var people: [Person]?
}
//Mark: - Page
extension People: Page{ /*** error 1 ***/
typealias PageItemType = Person
func itemAt(index: Int) -> Person{
let person : Person = self.people![index]
return person
}
}
Obtaining the following error (1):
Type 'People' does not conform to protocol 'Page'
Protocol requires nested type 'PageItemType'
I also tried making it explicit but i just got a different error:
//Mark: - Page
extension People: Page{
typealias PageItemType = Person
func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType{
let person : Person = self.people![index]
return person /*** error 2 ***/
}
}
Obtaining the following error (2):
Cannot convert return expression of type 'Person' to return type 'PageItemType'
So: *How can i let itemAt
function return a valid type for the PageItemType typealias?
* Bonus *
Bonus question worth a 50 bounty (if answer is longer than a row i'll open a new question):
Referring to the first code snippet PagedCollection
ItemType:Pageable
? Or at least enforce it with a where
clause?Upvotes: 2
Views: 2329
Reputation: 36427
This is merely a formatted discussion from my chat with Hamish.
I'm writing it here, because chatrooms can get archived and sometimes a more direct QA can convey things differently
Me:
protocol Provider {
associatedtype Input
associatedtype Output
func value(forKey _key: Input) -> Output{}
}
vs.
protocol Provider{
func value<Input, Output>(forKey _key: Input) -> Output{}
}
Hamish: Essentially the difference is where the placeholders are satisfied – associated types are satisfied at type level, whereas generic placeholders on a function are satisfied at the call-site of said function.
Me: I understand that level of difference. But is there any byproduct due to that difference :D
Hamish: What do you mean? they express different things – the former is a protocol where the conforming type only deals with one specific type of input and output, the latter is a protocol where the conforming type can deal with any given input and output types.
Me: The former is a protocol where the conforming type only deals with one specific type of input and output. What do you mean by specific? Can't the typeAlias
be anything I like?
Hamish: It can, but it can only be satisfied once per type – once I define
struct S : Provider {
func value(forKey: String) -> Int
}
S
now can only deal with String inputs and Int outputs.
Whereas with the latter, I would define
struct S : Provider {
func value<Input, Value>(forKey _key: Input) -> Output{}
}
now S
has to be able to deal with any input and output type.
Upvotes: 2
Reputation: 80941
It looks like you're conflating associated types with generic functions.
Generic functions allow you to provide a type to replace a given generic placeholder at the call site (i.e when you call the function).
Associated types allow types conforming to protocols to provide their own type to replace a given placeholder type in the protocol requirements. This is done per type, not at the call site of any function. If you wish to enforce a conformance requirement for an associatedtype
, you should do so directly on its declaration (i.e associatedtype PageItemType : Pageable
).
If I understand your requirements correctly, your itemAt(index:)
function should be non-generic (otherwise the associatedtype
in your protocol is completely redundant). The type that it returns is defined by the implementation of the type that conforms to Page
, rather than the caller of the function. For example, your People
class defines that the PageItemType
associated type should be a Person
– and that is what itemAt(index:)
should return.
protocol Pageable {/* ... */}
protocol Page {
// any conforming type to Page will need to define a
// concrete type for PageItemType, that conforms to Pageable
associatedtype PageItemType : Pageable
// returns the type that the implementation of the protocol defines
// to be PageItemType (it is merely a placeholder in the protocol decleration)
func itemAt(index: Int) -> PageItemType
}
class Person : Pageable {/* ... */}
class People {
var people: [Person]?
}
extension People : Page {
// explicitly satisfies the Page associatedtype requirement.
// this can be done implicitly be the itemAt(index:) method,
// so could be deleted (and annotate the return type of itemAt(index:) as Person)
typealias PageItemType = Person
// the itemAt(index:) method on People now returns a Person
func itemAt(index: Int) -> PageItemType {
// I would advise against force unwrapping here.
// Your people array most likely should be non-optional,
// with an initial value of an empty array
// (do you really need to distinguish between an empty array and no array?)
let person = self.people![index]
return person
}
}
In regards to your implementation of a PagedCollection
– as the PageItemType
associated type in your Page
protocol conforms to Pageable
, I see no need for an ItemType
generic parameter. This can simply be accessed through the associated type of the given PageType
generic parameter.
As an example:
class PagedCollection<PageType:Page> {
// PageType's associatedtype, which will conform to Pageable.
// In the case of the PageType generic parameter being People,
// PageType.PageItemType would be of type Person
var foo : PageType.PageItemType
init(foo: PageType.PageItemType) {
self.foo = foo
}
}
// p.foo is of type Person, and is guarenteed to conform to Pageable
let p = PagedCollection<People>(foo: Person())
Upvotes: 4