2017-06-06 27 views
1

在设计的API,我碰上这种在每一个角落:为什么使用Self或associatedtype对协议进行限制?

协议只能作为一种通用的约束,因为它具有自我或associatedType要求

这是什么意思是,你基本上可以在协议级别不使用Selfassociatedtype(如果您打算自行使用协议类型),否则需要通用的类型自身级联失控。

这很烦人(阅读:使得它几乎不可能设计合理的API,例如暴露协议但隐藏实现)。举例来说,你不能有一个简单的数据容器协议:

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

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

“官方”推荐的“解决方案”是使委托/包装结构消除类型变量,如AnyDataContainer。这感觉就像一个相当愚蠢,拙劣的解决方法,而不是适当的API设计。

我真的不明白为什么类型系统有这个限制。原因是什么?为什么编译器无法为每个协议P创建AnyP来解决它?

+2

我相信[我对前一个问题的回答](https://stackoverflow.com/a/41698579/2976878)基本上回答了这个问题 - 没有真正的原因,为什么这是不可能的,Swift只是没有还支持它。引用我在我的答案中引用的泛型声明“*对存在类型的限制来自实现限制,但即使协议具有自约束或关联类型,也允许协议类型的值是合理的。*“ – Hamish

+0

@Hamish我已经忘记了这一点,谢谢,不过我想知道是否还有其他问题,比如说,”我们“所有的东西都可以静态解决吗?有很多工具可以动态地处理类型*因此在这里可能会有一些真正的(或者至少是次要的)限制。例如,我没有看到编译器如何使用'Self'调用一个函数来正常工作不知道'Self'会解决什么问题,因为它基本上都想静态分派。 – Raphael

+1

如果需求有相关类型或'Self'需求(例如你的例子中的'=='),以便在类型为协议的实例上使用它们(当支持这种情况时),你确实需要给编译器一些具体的类型信息。在[“开放存在”一节中详细介绍了这种处理的一种方式](https ://github.com/apple/swift/blob/master/docs /GenericsManifesto.md#opening-existentials)泛型声明。 – Hamish

回答

0

下面是我能想到的一个原因,它实际上并不需要奥术安装。建立从问题的例子,假设我们可以有这样的代码:

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

雨燕编译器要静态调度==;但它不能!它没有单一的实现它可以拿起;每个数组元素可以具有其自己的类型,并且因此实现==,并且element可以具有完全不同的一个。因此编译器禁止手头的代码。


两个额外的想法。

  1. 我要说的是,编译器防守。下面的代码可以很容易地编写,如:

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

    在这里,我们不需要知道确切类型的数组元素;承诺他们每个人都有Int财产id就够了!

    所以它可以允许使用类型如[DataContainer]只要所有访问的属性都可以静态解析。

  2. 如果在调度和/或通用实现存在的地方没有歧义,编译器可以允许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) 
        } 
    } 
    

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

我不知道泛型协议的所有问题是否都可以用类似的想法完成,如果我们会沿着这条道路进一步限制。

0

认为它像文件说A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality

所以该协议只是说明需要什么来符合它而不是如何做。 “如何”的工作留给符合协议本身的任何内容。

这意味着:

1)不能使用的协议在它自己的,因为它没有实现本身。

2)您不能设计使用协议隐藏实现的API。

基本上协议是对任何实现它的东西说'这里是你需要做的,但是它取决于你如何去做'。

对于一个API,也许你会更好使用子类。

(当然,有很多方法可以弯曲的协议做这类事情,但是,你应该认真考虑,如果这是做到这一点的最好办法)

编辑:

您可以使用自在协议扩展中,但你不能以你想要的方式使用它。该协议是免费的

extension DataContainer { 
    func doubleID() -> Int { 
     return self.id * 2 
    } 
} 

所以,你可以隐藏一些实现,但那么这是一个默认为任何符合:我可以添加这协议:

func doubleID() -> Int 

,然后创建一个这样的扩展为doubleID定义它自己的方法。

编辑:

这将实现你是什么,虽然之后:

protocol TestProtocol: Equatable { 
    var id: Int { get } 
    var data: Data { get } 
} 

func ==<T: TestProtocol>(lhs: T, rhs: T) -> Bool { 
    return lhs.id == rhs.id 
} 

func ==<T: TestProtocol, U: TestProtocol>(lhs: T, rhs: U) -> Bool { 
    return lhs.id == rhs.id 
} 

func !=<T: TestProtocol, U: TestProtocol>(lhs: T, rhs: U) -> Bool { 
    return lhs.id != rhs.id 
} 

这将提供任何默认平等的实现,符合TestProtocol但它不会停止任何与自己重写实现。

+0

感谢您的时间,但我不认为这个广泛的答案,好吧,回答我的问题。我意识到协议的高级概念,这是关于一些“Swift不允许我们声明(实际上)的东西”。 – Raphael

+0

关于子类,因为他们称Swift为“面向协议”,我相信这不是答案。特别是如此,因为结构的使用被明确地鼓励,并且没有从结构继承。 – Raphael

+0

广告编辑1:这是关于“自我”,而不是“自我”。广告编辑2:您仍然无法声明'[TestProtocol]';即使编译器对手头的问题更聪明,它仍然完全不清楚应该静态选择哪个实现。 – Raphael

相关问题