swift - 为什么要限制带有 Self 或 associatedtype 的协议(protocol)?

标签 swift generics types swift-protocols

在设计 API 时,我在每个角落都会遇到这个问题:

Protocol can only be used as a generic constraint because it has Self or associatedType requirements

这意味着如果您计划单独使用协议(protocol)类型,您基本上不能在协议(protocol)级别使用 Selfassociatedtype —— 否则需要类型本身是通用的级联失控。

这很烦人(阅读:几乎不可能设计合理的 API,例如公开协议(protocol)但隐藏实现)。例如,你不能有一个简单的数据容器协议(protocol):

protocol DataContainer: Equatable {
    var id: Int { get }
    var content: Data { get }
}

问题是 Equatable 需要静态函数 ==,当然,它是使用 Self 表示的。因此,您不能拥有像 [DataContainer] 这样的类型,或任何数量的其他自然事物。我首先遇到了用户组(数组)(可能是三种不同类型之一)。

“官方”推荐的“解决方案”是使用委托(delegate)/包装结构来消除类型变量,例如 AnyDataContainer。这感觉像是一个相当愚蠢的变通方法,不是正确的 API 设计。

老实说,我不明白为什么类型系统有这个限制。原因是什么?为什么编译器不能为每个协议(protocol) P 隐式创建 AnyP 来绕过它?

最佳答案

这是我能想到的一个原因,实际上也不需要神秘的设置。基于问题中的示例,假设我们可以有这样的代码:

func contained(element: DataContainer, in list: [DataContainer]) -> Bool {
    for elem in list {
        if elem == element {
            return true
        }
    }
    return false
}

Swift 编译器想要静态分派(dispatch) ==;但它不能!它没有可以选择的单一实现;每个数组元素都可能有自己的类型,因此 == 的实现,而 element 可能有一个完全不同的类型。因此,编译器禁止使用手头的代码。


另外两个想法。

  1. 我认为编译器过于具有防御性。下面的代码可以很容易地编译,例如:

    func contained(id: Int, in list: [DataContainer]) -> Bool {
        for elem in list {
            if elem.id == id {
                return true
            }
        }
        return false
    }
    

    在这里,我们不需要知道数组元素的确切类型;他们每个人都有一个 Int 属性 id 的 promise 就足够了!

    因此它可以允许使用像 [DataContainer] 这样的类型,只要所有访问的属性都可以静态解析。

  2. 如果在何处分派(dispatch)和/或存在通用实现没有歧义,编译器可以允许 Self 引用。例如(伪代码):

    extension DataContainer {
        static func ==<T: DataCointainer, U: DataContainer>(lhs: T, rhs:U) -> Bool {
            guard T == U else {
                return false
            }
    
            return T.==(lhs, rhs as! T)
        }
    }
    

    这样的默认实现可以在上述使用上下文中使用,即如果没有足够的类型信息可用于静态选择实现。

我不知道是否可以通过类似的想法解决通用协议(protocol)的所有问题,以及我们是否会在此过程中遇到进一步的限制。

关于swift - 为什么要限制带有 Self 或 associatedtype 的协议(protocol)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44389942/

相关文章:

java - 如何在工厂模式中编写通用方法来提供对象?

java - 读取Java泛型类的注解

javascript - 避免在 typescript 中添加字符串和数字?

ios - AlamofireCoreData Pod

swift - 将 UInt16 转换为 Int16 会发生程序崩溃

Java 泛型传递 .class 引用

java - 操作大文本 block

curl - 为什么我不能将切片更改为 H512?

swift - 根据内容自动调整基于 NSTableView 的 View 的宽度

ios - 将完成 block 添加到 enumerateObjectsWithOptions