1

* 精简版 *

如何使类(扩展)符合通用协议函数?

* 长版 *

这是支持分页集合的数据结构的一小部分,

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> {
    //...
//}

这是具有“真实”案例的协议的实现:

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
    }
}

得到如下错误(1):

类型“人”不符合协议“页面”

协议需要嵌套类型“PageItemType”

我也试过让它明确,但我得到了一个不同的错误:

//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 ***/
    }
}

得到如下错误(2):

无法将“Person”类型的返回表达式转换为“PageItemType”类型

所以:*如何让itemAt函数返回 PageItemType 类型别名的有效类型?

* 奖金 *

价值 50 的奖励问题(如果答案超过一行,我将打开一个新问题):参考第一个代码片段PagedCollection

  • 假设每个 Page 实现总是有一个已知的 Pageable 协议对象类型的实现
  • 有没有办法避免声明ItemType:Pageable?或者至少用一个where条款来强制执行它?
4

2 回答 2

4

看起来您正在将关联类型与泛型函数混为一谈。

泛型函数允许您提供一种类型来替换调用站点上给定的泛型占位符(即,当您调用该函数时)。

关联类型允许符合协议的类型提供自己的类型来替换协议要求中给定的占位符类型。这是按 type完成的,而不是在任何函数的调用站点。如果您希望对 强制执行一致性要求associatedtype,您应该直接在其声明中执行此操作(即associatedtype PageItemType : Pageable)。

如果我正确理解您的要求,则您的itemAt(index:)功能应该是非通用的(否则associatedtype您的协议中的功能完全是多余的)。它返回的类型由符合 的类型的实现定义Page,而不是函数的调用者。例如,您的People类定义PageItemType关联的类型应该是Person- 并且itemAt(index:)应该返回。

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
    }
}

关于您对 a PagedCollection–的实现,因为PageItemType您的协议中的关联类型Page符合Pageable,我认为不需要ItemType泛型参数。这可以通过给定PageType泛型参数的关联类型简单地访问。

举个例子:

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())
于 2016-09-16T20:47:44.790 回答
1

这只是我与 Hamish聊天的格式化讨论。

我在这里写它,因为聊天室可以存档,有时更直接的 QA 可以以不同的方式传达信息

我:

protocol Provider { 
    associatedtype Input 
    associatedtype Output 
    func value(forKey _key: Input) -> Output{} 
}

对比

protocol Provider{ 
    func value<Input, Output>(forKey _key: Input) -> Output{} 
}

Hamish:本质上的区别在于占位符在哪里得到满足——关联类型在类型级别得到满足,而函数上的通用占位符在所述函数的调用点得到满足。

我:我理解那种程度的差异。但是由于这种差异是否有任何副产品:D

哈米什:你什么意思?它们表达了不同的东西——前者是一种协议,其中一致性类型只处理一种特定类型的输入和输出,后者是一种协议,一致性类型可以处理任何给定的输入和输出类型。

我: 前者是一种协议,其中一致性类型只处理一种特定类型的输入和输出。你说的具体是什么意思?不能typeAlias是我喜欢的东西吗?

Hamish:可以,但每种类型只能满足一次——一旦我定义

struct S : Provider { 
   func value(forKey: String) -> Int 
}

S现在只能处理 String 输入和 Int 输出。而对于后者,我会定义

struct S : Provider { 
    func value<Input, Value>(forKey _key: Input) -> Output{} 
} 

现在S必须能够处理任何输入和输出类型。

于 2018-08-22T18:53:56.800 回答