【Swift】Equatableプロトコルのヘルプドキュメントを読む


# この記事は何？
Swiftプログラミングにおける`Equatable`プロトコルについて、公式のヘルプドキュメントを翻訳および解説します。

参考: [Equatable | Apple Developer Documentation](https://developer.apple.com/documentation/swift/equatable)

以下の概念を理解しておくと、より理解が深まります。
* エクステンション
* 型メソッド
* 型の制約

> Swiftを基礎から学ぶには
自著、工学社より発売中の「[まるごと分かるSwiftプログラミング](https://amzn.asia/d/7YPWJwl)」をお勧めします。変数、関数、フロー制御構文、データ構造はもちろん、構造体からクロージャ、エクステンション、プロトコル、クロージャまでを基礎からわかりやすく解説しています。

## 宣言

```swift
protocol Equatable
```

## 概要

`Equatable`プロトコルに適合する型は等価演算子`==`を使用して、「値が等しいかどうか」を比較できます。
また、不等価演算子`!=`を使用して、「値が等しくないかどうか」を比較できます。
Swift標準ライブラリの基本的な型は、ほとんどが`Equatable`プロトコルに適合しています。

要素が`Equatable`に適合している場合、シーケンスやコレクションの操作をより簡単に行えます。
例えば、配列が「特定の値を含むかどうか」を調べるためには「等価性を決定するクロージャ」を提供するかもしれません。
しかしながら、配列の要素が`Equatable`プロトコルに適合していれば、「調べたい値」を配列の`contains(_:)`メソッドに渡すことができます。
次の例は、文字列の配列で`contains(_:)`メソッドを使用する方法を示しています。

```swift
let students = ["Kofi", "Abena", "Efua", "Kweku", "Akosua"]

let nameToCheck = "Kofi"
if students.contains(nameToCheck) {
    print("\(nameToCheck) is signed up!")
} else {
    print("No record of \(nameToCheck).")
}
// Prints "Kofi is signed up!"
```

## Equatableプロトコルへの適合

カスタム型を`Equatable`プロトコルに適合させると、より便利なAPIを利用して「コレクション内の特定のインスタンスを検索する」ことができるようになります。
`Equatable`は、`Hashable`および`Comparable`の基礎となるプロトコルです。
そして、セットを構築したり、コレクションの要素をソートするなど、カスタム型をより多く使い方ができるようにします。
カスタム型の定義において、`Equatable`適合を宣言し、その型が以下に挙げる基準を満たしている場合、`Equatable`プロトコルの要件を満たす実装が自動的に生成されます。

* 構造体の場合、すべての格納プロパティが`Equatable`に適合している。
* 列挙型の場合、すべての関連値が`Equatable`に適合している (関連値がなければ宣言なしで `Equatable`に適合)。

型の`Equatable`適合性をカスタマイズしたり、上記の基準を満たさない型に`Equatable`を採用したり、既存の型を拡張して`Equatable`に適合させたりするには、型に静的メソッドとして等価演算子`==`を実装します。
なお、標準ライブラリは`Equatable`型に対して不等価演算子`!=`の実装を提供しており、これはカスタムした`==`関数を呼び出して、その結果を論理否定します。

例として、住所を表す家屋や建物番号、通りの名称、およびオプションのユニット番号を保持する`StreetAddress`クラスを考えます。
まずは、`StreetAddress`型を宣言します。

```swift
class StreetAddress {
    let number: String
    let street: String
    let unit: String?

    init(_ number: String, _ street: String, unit: String? = nil) {
        self.number = number
        self.street = street
        self.unit = unit
    }
}
```

ここで、「住所の配列」に特定の住所が含まれているかチェックしたいとします。
クロージャを要素ごとに呼び出す方法ではなく、 `contains(_:)`メソッドを使用できるようにしましょう。
そのためには、要素の`StreetAddress`型を拡張して`Equatable`プロトコルに適合させます。

```swift
extension StreetAddress: Equatable {
    static func == (lhs: StreetAddress, rhs: StreetAddress) -> Bool {
        return
            lhs.number == rhs.number &&
            lhs.street == rhs.street &&
            lhs.unit == rhs.unit
    }
}
```

このエクステンションによって、`StreetAddress`型は`Equatable`に適合しました。
これにより、等価演算子`==`を使用して「2つのインスタンスが等しいかどうか」をチェックしたり、`Equatable`に制約された`contains(_:)`メソッドを呼び出したりすることができるようになりました。

```swift
let addresses = [StreetAddress("1490", "Grove Street"),
                 StreetAddress("2119", "Maple Avenue"),
                 StreetAddress("1400", "16th Street")]
let home = StreetAddress("1400", "16th Street")

print(addresses[0] == home)
// Prints "false"
print(addresses.contains(home))
// Prints "true"
```

同等に比較される2つのインスタンスは、その値に依存するすべてのコードで互換性を持って使用できます。
置換性を維持するために、等価演算子`==`は`Equatable`な型のすべての可視的な側面を考慮する必要があります。
クラスID以外の`Equatable`型の非値側面を公開することは推奨されず、公開されるものはドキュメントで明示的に指摘する必要があります。

`Equatable`な型のインスタンス間の等価性は等価関係であるため、`Equatable`に適合するカスタム型は、任意の値`a`、`b`、`c`に対して、次の3つの条件を満たす必要があります。

* `a == a` は常に`ture`になる (Reflexivity)
* `a == b` なら `b == a` になる (対称性)
* `a == b` かつ `b == c` なら `a == c` になる (Transitivity)

さらに、不等式は等式の逆であるため、`!=`演算子のカスタム実装は、`a != b` が `!(a == b)` となることを保証しなければなりません。
デフォルトの`!=`演算子関数の実装は、この要件を満たしています。

## 等価性と同一性の違い

クラスにおいて、インスタンスの同一性は「値の一部」ではありません。
整数値をラップした`IntegerRef`クラスを考えてみましょう。
`IntegerRef`型の定義と、それを`Equatable`に適合させる`==`関数を以下に示します。

```swift
class IntegerRef: Equatable {
    let value: Int
    init(_ value: Int) {
        self.value = value
    }

    static func == (lhs: IntegerRef, rhs: IntegerRef) -> Bool {
        return lhs.value == rhs.value
    }
}
```

この`==`関数の実装は、`value`プロパティに格納されている整数が等しいければ、2つの引数が同じインスタンスであろうとなかろうと、返り値には影響しません。
たとえば...

```swift
let a = IntegerRef(100)    // bとは別の参照先
let b = IntegerRef(100)    // aとは別の参照先

print(a == a, a == b, separator: ", ")    // 参照先は異なるが、値は等しい
// Prints "true, true"
```

一方、クラスの「インスタンスが同一かどうか」は同一演算子`===`によって比較されます。
たとえば...

```swift
let c = a    // cは、aと同じ参照先を指す
print(c === a, c === b, separator: ", ")    // aとcは参照先が同じだが、bとは異なる
// Prints "true, false"
```