目标C要求我们对平等和身份的本质进行**哲学**思考。令任何不太倾向于散漫论文的开发者欣慰的是,斯威夫特的情况并非如此。
在斯威夫特,有一个赤道协议,它以完全独立于身份问题的方式明确定义了平等和不平等的语义学。还有可比协议,它建立在赤道的基础上,将不平等语义学细化为创建价值排序。可比较协议和可比协议一起构成了整个语言的中心比较点。
等价的
符合均衡协议的值可以被评估为相等和不相等。符合等式要求实现等号操作符(==)。
作为一个例子,考虑下面的**Binomen结构**:
struct Binomen { let genus: String let species: String}let 🐺 = Binomen(genus: "Canis", species: "lupus")let 🐻 = Binomen(genus: "Ursus", species: "arctos")
我们可以通过扩展来添加Equable一致性,实现==运算符所需的类型方法,如下所示:
extension Binomen: Equatable { static func == (lhs: Binomen, rhs: Binomen) -> Bool { return lhs.genus == rhs.genus && lhs.species == rhs.species }}🐺 == 🐺 // true🐺 == 🐻 // false
很简单,对吧?
实际上,比这更容易——从Swift 4.1开始,编译器可以自动合成存储属性都具有可等式类型的结构的一致性。我们可以通过简单地在Binomen宣言中采用Equable来替换扩展中的所有代码:
struct Binomen: Equatable { let genus: String let species: String}🐺 == 🐺 // true🐺 == 🐻 // false
平等的好处
可等式不仅仅是使用==运算符——还有!=运算符!~它还允许在集合中找到一个值,并在开关语句中进行匹配。
[🐺, 🐻].contains(🐻) // truefunc commonName(for binomen: Binomen) -> String? { switch binomen { case 🐺: return "gray wolf" case 🐻: return "brown bear" default: return nil }}commonName(for: 🐺) // "gray wolf"
等式也是符合Hasble**的**要求,Hasble是Swift中的另一个重要类型。
这一切都是说,如果一个类型具有相等的语义学——如果该类型的两个值可以被认为相等或不相等——它应该符合赤道。
自动合成的局限性
Swift标准库和Apple SDK中的大多数框架都很好地为有意义的类型采用了赤道。实际上,你不太可能处于这样一种情况,即内置类型的失职会破坏你自己类型的自动合成。
相反,自动合成最常见的障碍涉及元组。想想这个**考虑不周的Trinomen类型**:
struct Trinomen { let genus: String let species: (String, subspecies: String?) // 🤔}extension Trinomen: Equatable {}// 🛑 Type 'Trinomen' does not conform to protocol 'Equatable'
正如我们在关于**Void的**文章中所描述的,元组不是名义类型,所以它们不能符合Equable。如果你想比较两个trinomina是否相等,你必须为Equable编写一致性代码。
……像某种动物。
平等的条件一致性
除了自动合成Equable之外,Swift 4.1还增加了另一个关键特性:条件一致性。
为了说明这一点,请考虑以下表示某物数量的泛型类型:
struct Quantity<Thing> { let count: Int let thing: Thing}
数量能符合等式吗?我们知道整数是相等的,所以这真的取决于我们在谈论什么样的东西。
条件一致性Swift 4.1允许我们在具有条件子句的类型上创建扩展。我们可以在这里用它来编程地表达_“如果事物本身是相等的,那么事物的数量是相等的”:
extension Quantity: Equatable where Thing: Equatable {}
仅凭这一声明,Swift就拥有了综合条件等式一致性所需的一切,允许我们做到以下几点:
let oneHen = Quantity<Character>(count: 1, thing: "🐔")let twoDucks = Quantity<Character>(count: 2, thing: "🦆")oneHen == twoDucks // false
参照平等
对于引用类型,相等的概念与标识混为一谈。两个具有相同值的Name结构是相等的,但是两个Person对象可以具有相同的名称,并且仍然是不同的人,这是有意义的。
对于目标C兼容的对象类型,==运算符已经从isEqual:方法中提供**:**
import Foundationclass ObjCObject: NSObject {}ObjCObject() == ObjCObject() // false
对于Swift引用类型(即类),可以使用等号操作符(===)来计算相等性:
class Object: Equatable { static func == (lhs: Object, rhs: Object) -> Bool { return lhs === rhs }}Object() == Object() // false
也就是说,引用类型的等式语义学通常不如直接的身份检查那么简单,所以在您将一致性添加到所有类之前,问问自己这样做是否真的有意义。
可比的
基于Equable,可比协议允许将值视为小于或大于其他值。
可比需要以下运算符的实现:
OperatorName<Less than<=Less than or equal to>=Greater than or equal to>Greater than
……所以令人惊讶的是,你只能实现其中一个:<运算符。
回到我们的二项命名示例,让我们扩展二项命名以符合可比,这样值首先按字母顺序按属名排序,然后按物种名排序:
extension Binomen: Comparable { static func < (lhs: Binomen, rhs: Binomen) -> Bool { if lhs.genus != rhs.genus { return lhs.genus < rhs.genus } else { return lhs.species < rhs.species } }}🐻 > 🐺 // true ("Ursus" lexicographically follows "Canis")
这是相当聪明的。由于每个比较运算符的实现都可以从<和==派生出来,所以所有这些功能都是通过类型推断自动提供的。
无与伦比的限制与不平等
与等式不同,Swift编译器不能自动合成与可比的一致性。但这并不是因为缺乏尝试——这是不可能的。
对于可比性,没有隐式语义学可以从存储属性的类型中导出。如果一个类型具有多个存储属性,则无法确定它们之间的相对比较方式。即使一个类型只有一个类型是可比的属性,也不能保证该属性的排序与整个值的排序有什么关系
可比利益
与可比一致会带来许多好处。
一个这样的好处是,包含可比类型值的数组可以调用诸如orted()、min()和max()的方法:
let 🐬 = Binomen(genus: "Tursiops", species: "truncatus")let 🌻 = Binomen(genus: "Helianthus", species: "annuus")let 🍄 = Binomen(genus: "Amanita", species: "muscaria")let 🐶 = Binomen(genus: "Canis", species: "domesticus")let menagerie = [🐺, 🐻, 🐬, 🌻, 🍄, 🐶]menagerie.sorted() // [🍄, 🐶, 🐺, 🌻, 🐬, 🐻]menagerie.min() // 🍄menagerie.max() // 🐻
具有定义的顺序还可以创建范围,如下所示:
let lessThan10 = ..<10lessThan10.contains(1) // truelessThan10.contains(11) // falselet oneToFive = 1...5oneToFive.contains(3) // trueoneToFive.contains(7) // false
在Swift标准库中,Equable是一个没有相等的类型;比较是一个没有比较的协议。注意在你自己的类型中适当地采用它们,你会受益匪浅。