* 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?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())