【译】等价和可比

179 阅读5分钟

原文链接 Equatable and Comparable

目标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是一个没有相等的类型;比较是一个没有比较的协议。注意在你自己的类型中适当地采用它们,你会受益匪浅。