简体   繁体   English

在具有自我类型要求的协议上键入擦除

[英]Type erasure on protocol with Self type requirement

I have a protocol named Shape that conforms to Comparable . 我有一个名为Shape的协议符合Comparable Ultimately I would like to create an array of whatever conformed to this protocol, even if they are not the same sub type. 最终我想创建一个符合此协议的数组,即使它们不是同一个子类型。

I created some classes conforming to Shape , namely Triangle , Square and Rectangle . 我创建了一些符合Shape类,即TriangleSquareRectangle What I want to do is to define another class called Drawing that can accept an array of any Shape. 我想要做的是定义另一个名为Drawing类,它可以接受任何Shape的数组。

//: Playground - noun: a place where people can play
import Foundation
import UIKit

protocol Shape: Comparable {
    var area: Double { get }
}

extension Shape {
    static func < (lhs: Self, rhs: Self) -> Bool {
        return lhs.area < rhs.area
    }

    static func == (lhs: Self, rhs: Self) -> Bool {
        return lhs.area == rhs.area
    }
}

class Triangle: Shape {
    let base: Double
    let height: Double

    var area: Double { get { return base * height / 2 } }

    init(base: Double, height: Double) {
        self.base = base
        self.height = height
    }
}

class Rectangle: Shape {
    let firstSide: Double
    let secondSide: Double

    var area: Double { get { return firstSide * secondSide } }

    init(firstSide: Double, secondSide: Double) {
        self.firstSide = firstSide
        self.secondSide = secondSide
    }
}

class Square: Rectangle {
    init(side:  Double) {
        super.init(firstSide: side, secondSide: side)
    }
}

class Drawing {
    //Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements
    let shapes: [Shape]
    init(shapes: [Shape]) {
        self.shapes = shapes
    }
}

However, when I try to use Shape as the type of the array, I get the following error Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements . 但是,当我尝试使用Shape作为数组的类型时,我得到以下错误Protocol 'Shape' can only be used as a generic constraint because it has Self or associated type requirements How can I declare an array containing any type of shapes? 如何声明包含任何类型形状的数组?

You have hit just about every basic mistake you can hit in designing protocols, but they are all extremely common mistakes and not surprising. 你已经解决了设计协议中可能遇到的每一个基本错误,但它们都是极其常见的错误并不奇怪。 Everyone does it this way when they start, and it takes awhile to get your head in the right space. 当他们开始时,每个人都这样做,并且需要一段时间才能让你的头脑在正确的空间里。 I've been thinking about this problem for almost four years now, and give talks on the subject, and I still mess it up and have to back up and redesign my protocols because I fall into the same mistakes. 我一直在思考这个问题差不多四年,并给会谈关于这个问题,我还是搞砸了,并有备份和重新设计我的协议,因为我陷入了同样的错误。

Protocols are not a replacement for abstract classes. 协议不是抽象类的替代品。 There are two completely different kinds of protocols: simple protocols, and PATs (protocols with associated type). 有两种完全不同的协议:简单协议和PAT(具有相关类型的协议)。

A simple protocol represents a concrete interface, and can be used in some of the ways you might use an abstract class in other languages, but is best thought of as a list of requirements. 简单的协议代表一个具体的接口,可以在某些方法中使用,在其他语言中使用抽象类,但最好将其视为需求列表。 That said, you can think of a simple protocol as if it were a type (it actually becomes an existential, but it's pretty close). 也就是说,你可以把一个简单的协议想象成一个类型(它实际上变成了一个存在主义,但它非常接近)。

A PAT is a tool for constraining other types so that you can give those types additional methods, or pass them to generic algorithms. PAT是一种用于约束其他类型的工具,以便您可以为这些类型提供其他方法,或将它们传递给通用算法。 But a PAT is not a type. 但PAT不是一种类型。 It can't be put in an Array. 它不能放在一个数组中。 It can't be passed to a function. 它无法传递给函数。 It cannot be held in a variable. 它不能保存在变量中。 It is not a type. 它不是一种类型。 There is no such thing as "a Comparable." 没有“可比的”这样的东西。 There are types that conform to Comparable. 有些类型符合 Comparable。

It is possible to use type erasers to force a PAT into a concrete type, but it is almost always a mistake and very inflexible, and it's particularly bad if you have to invent a new type eraser to do it. 可以使用类型擦除器强制PAT成为具体类型,但它几乎总是一个错误而且非常不灵活,如果你必须发明一种新型橡皮擦来做这件事,那就特别糟糕了。 As a rule (and there are exceptions), assume that if you're reaching for a type eraser you've probably mis-designed your protocols. 作为一项规则(也有例外),假设如果您要使用类型橡皮擦,您可能错误地设计了协议。

When you made Comparable (and through it Equatable) a requirement of Shape, you said that Shape is a PAT. 当你制作Comparable(并通过它Equatable)一个Shape的要求时,你说Shape是一个PAT。 You didn't want that. 你不想那样。 But then again, you didn't want Shape. 但话说回来,你不想要Shape。

It's difficult to know precisely how to design this, because you don't show any use cases. 很难确切地知道如何设计它,因为您没有显示任何用例。 Protocols emerge from use cases. 协议来自用例。 They do not spring up from the model typically. 它们通常不会从模型中涌现出来。 So I'll provide how you get started, and then we can talk about how to implement further pieces based on what you would do with it. 所以我将提供你如何开始,然后我们可以讨论如何根据你将用它做什么来实现更多的部分。

First, you would model these kinds of shapes a value types. 首先,您可以将这些形状建模为值类型。 They're just data. 他们只是数据。 There's no reason for reference semantics (classes). 参考语义(类)没有理由。

struct Triangle: Equatable {
    var base: Double
    var height: Double
}

struct Rectangle: Equatable {
    var firstSide: Double
    var secondSide: Double
}

I've deleted Square because it's a very bad example. 我删除了Square,因为这是一个非常糟糕的例子。 Squares are not properly rectangles in inheritance models (see the Circle-Ellipse Problem ). 在继承模型中,正方形不是正确的矩形(请参阅圆椭圆问题 )。 You happen to get away with it using immutable data, but immutable data is not the norm in Swift. 你碰巧使用不可变数据来逃避它,但不可变数据并不是Swift的常态。

It seems you'd like to compute area on these, so I assume there's some algorithm that cares about that. 看起来你想在这些上计算面积,所以我假设有一些关心它的算法。 It could work on "regions that provide an area." 它可以用于“提供区域的区域”。

protocol Region {
    var area: Double { get }
}

And we can say that Triangles and Rectangles conform to Region through retroactive modeling. 我们可以说三角形和矩形通过追溯建模符合Region。 This can be done anywhere; 这可以在任何地方完成; it doesn't have to be decided at the time that the models are created. 在创建模型时不必决定它。

extension Triangle: Region {
    var area: Double { get { return base * height / 2 } }
}

extension Rectangle: Region {
    var area: Double { get { return firstSide * secondSide } }
}

Now Region is a simple protocol, so there's no problem putting it in an array: 现在Region是一个简单的协议,所以把它放在一个数组中是没有问题的:

struct Drawing {
    var areas: [Region]
}

That leaves the original question of equality. 这留下了原始的平等问题。 That has lots of subtleties. 这有很多细微之处。 The first, and most important, is that in Swift "equals" (at least when tied to the Equatable protocol) means "can be substituted for any purpose." 第一个也是最重要的是,在Swift中“等于”(至少在与Equatable协议相关时)意味着“可以替代任何目的”。 So if you say "triangle == rectangle" you have to mean "in any context that this triangle could be used, you're free to use the rectangle instead." 因此,如果你说“triangle == rectangle”你必须指“在任何情况下都可以使用这个三角形,你可以自由地使用矩形。” The fact that they happen to have the same area doesn't seem a very useful way to define that substitution. 它们恰好具有相同区域的事实似乎不是定义该替换的非常有用的方式。

Similarly it is not meaningful to say "a triangle is less than a rectangle." 同样地说“三角形小于矩形”也没有意义。 What is meaningful is to say that a triangle's area is less than a rectangle's, but that's just means the type of Area conforms to Comparable , not the shapes themselves. 有意义的是说三角形的面积小于一个矩形,但这只是意味着Area的类型符合Comparable ,而不是形状本身。 (In your example, Area is equivalent to Double .) (在您的示例中, Area等于Double 。)

There are definitely ways to go forward and test for equality (or something similar to equality) among Regions, but it highly depends on how you plan to use it. 肯定有各种方法可以推进并测试各地区之间的平等(或类似平等),但这在很大程度上取决于您计划如何使用它。 It doesn't spring naturally from the model; 它不会从模型中自然地弹出; it depends on your use case. 这取决于你的用例。 The power of Swift is that it allows the same model objects to be conformed to many different protocols, supporting many different use cases. Swift的强大之处在于它允许相同的模型对象符合许多不同的协议,支持许多不同的用例。

If you can give some more pointers of where you're going with this example (what the calling code would look like), then I can expand on that. 如果你能给出一些关于你在这个例子中的更多指针(调用代码的样子),那么我可以扩展它。 In particular, start by fleshing out Drawing a little bit. 特别是,通过梳理开始Drawing一点点。 If you never access the array, then it doesn't matter what you put in it. 如果你从不访问数组,那么你输入的内容并不重要。 What would a desirable for loop look like over that array? 将理想什么for循环看起来像在这阵?

The example you're working on is almost exactly the example used in the most famous protocol-oriented programming talk: Protocol-Oriented Programming in Swift , also called "the Crusty talk." 您正在处理的示例几乎就是最着名的面向协议的编程演讲中使用的示例: Swift中的面向协议编程 ,也称为“Crusty talk”。 That's a good place to start understanding how to think in Swift. 这是开始理解如何在Swift中思考的好地方。 I'm sure it will raise even more questions. 我相信它会引发更多问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM