[Swift] classにEquatableを実装するのは一筋縄ではいかない(ことがある)点に注意。

# はじめに
Swiftという言語にはProtocolというとても便利なシステムがあって、特にその中でも[`Equatable`](https://developer.apple.com/documentation/swift/equatable)は最も使われているProtocolだと思うのです[^mostUsedProtocolIsEquatable]。
`Equatable`をオリジナルのクラスに適用するには、単に2つのインスタンスが同じ値(とみなせる)かどうかを判断する`==`というstatic funcさえ実装すればいいのです[^sourceOfEquatableAtGitHub]が、openなクラスに実装する場合は多少工夫が必要です。この記事では、工夫が必要な場面とその工夫を紹介します。

[^mostUsedProtocolIsEquatable]: 私見。[Comparable](https://developer.apple.com/documentation/swift/comparable)や[Hashable](https://developer.apple.com/documentation/swift/hashable)などもEquatableを継承しているから、直接ではなくても皆んな使用しているよね？
[^sourceOfEquatableAtGitHub]: Swift 4.0-dev 現在: https://github.com/apple/swift/blob/swift-4.0-branch/stdlib/public/core/Equatable.swift#L156

# 実例

## `MyClass`に`Equatable`を適用する

次のような`MyClass`を考えてみましょう

```swift:myclass.swift
open class MyClass {
  public var a:Int
  public init(a:Int) { self.a = a }
}
```

`Equatable`を`MyClass`に適用するにあたって、`MyClass`のインスタンスは`.a`の値が同じなら同じであるとみなしましょう。

```swift:myclass-eq.swift
extension MyClass: Equatable {
  public static func ==(lhs:MyClass, rhs:MyClass) -> Bool {
    return lhs.a == rhs.a
  }
}
```

ね？簡単でしょ。

```swift:test-myclass-eq.swift
let object1 = MyClass(a:0)
let object2 = MyClass(a:1)
let object3 = MyClass(a:0)

print(object1 == object2) // -> false
print(object1 == object3) // -> true
```

## `MyClass`を継承した`MySubclass`を作ってみよう

`MyClass`を継承した`MySubclass`を定義します。新たなるインスタンス変数として`.b`を追加します。

```swift:mysubclass.swift
open class MySubclass: MyClass {
  public var b: Int
  public init(a:Int, b:Int) { self.b = b; super.init(a:a) }
}
```

`MyClass`が`Equatable`を適用しているので、それを継承した`MySubclass`も`==`演算子を使うことができます。**が、しかし…**

```swift:test-mysubclass.swift
let object4 = MySubclass(a:0, b:1)
let object5 = MySubclass(a:0, b:2)
let object6 = MySubclass(a:0, b:1)

print(object4 == object5) // -> true!!
print(object4 == object6) // -> true
```

当たり前と言えば当たり前なのですが、`MyClass`で定義した`==`演算子は`.a`というインスタンス変数しか比較をしていないので、`.b`の値が違っても`true`を返します。サブクラスの役割としてそれでいい場合もあるかもしれませんが、ここでは`.b`という値も同一でないといけないということにしましょう。

```swift:mysubclass-eq.swift
extension MySubclass {
  public static func ==(lhs:MySubclass, rhs:MySubclass) -> Bool {
    return lhs as MyClass == rhs as MyClass && lhs.b == rhs.b
  }
}
```

こうすることで、先ほどの`print(object4 == object5)`は期待通り`false`と表示されるはずです。なお、今回の例では、`lhs as MyClass == rhs as MyClass`の部分は`lhs.a == rhs.a`としても構いませんが、親クラスである`MyClass`に比較すべきインスタンス変数がたくさんある場合や後から比較すべきインスタンス変数を追加した場合などを考えると、前者の方が読みやすさと保守性の点で優れているでしょう。

## だが、これで終わりではない

ここまでのソースを振り返って見ましょう。

```swift:myclass-and-mysubclass.swift
open class MyClass {
  public var a:Int
  public init(a:Int) { self.a = a }
}
extension MyClass: Equatable {
  public static func ==(lhs:MyClass, rhs:MyClass) -> Bool {
    return lhs.a == rhs.a
  }
}

open class MySubclass: MyClass {
  public var b: Int
  public init(a:Int, b:Int) { self.b = b; super.init(a:a) }
}
extension MySubclass {
  public static func ==(lhs:MySubclass, rhs:MySubclass) -> Bool {
    return lhs as MyClass == rhs as MyClass && lhs.b == rhs.b
  }
}
```

`MyClass`でも`MySubclass`でも`Equatable`を適用できていて完璧にみえます。**ですが、まだ落とし穴があります。**
ここで、`MyClass`またはその子孫クラスのインスタンスを保持するラッパー`MyWrapper`を登場させてみましょう。

```swift:mywrapper.swift
public struct MyWrapper {
  public var object: MyClass
  public init(object:MyClass) { self.object = object }
}
```

こうなると`MyWrapper`も`Equatable`にしたいですよね？さぁ、してみましょう。

```swift:mywrapper-eq.swift
extension MyWrapper: Equatable {
  public static func ==(lhs:MyWrapper, rhs:MyWrapper) -> Bool {
    return lhs.object == rhs.object
  }
}
```

これでOKでしょうか？先ほどの`object1, 4, 5`に再度登場してもらいましょう。

```swift:test-mywrapper.swift
let object1 = MyClass(a:0)
let object4 = MySubclass(a:0, b:1)
let object5 = MySubclass(a:0, b:2)

let wrapper1 = MyWrapper(object:object1)
let wrapper4 = MyWrapper(object:object4)
let wrapper5 = MyWrapper(object:object5)

print(wrapper1 == wrapper4) // -> true!!
print(wrapper4 == wrapper5) // -> true!!
```

はて？
`object1`,`object4`,`object5`はそれぞれ全てが違うと判断されるべきインスタンスですが、ラッパーで包んだ途端、同一とみなされてしまいました。
これも当たり前と言えば当たり前なのですが、インスタンス変数が`public var object: MyClass`と定義されているので、`MyWrapper`の`static func ==`内にある`lhs.object == rhs.object`は、`MyClass`の`==`が呼ばれてしまうのです。`.object`が`MySubclass`のインスタンスの場合はそれとして比較するためにはどうすればいいのでしょうか？

### 案①

`MyWrapper`の実装を工夫してみる。

```swift:mywrapper-eq-revised.swift
extension MyWrapper: Equatable {
  public static func ==(lhs:MyWrapper, rhs:MyWrapper) -> Bool {
    let (lobj, robj) = (lhs.object, rhs.object)
    switch (type(of:lobj), type(of:robj)) {
    // lobjとrobjのクラスが一致する場合のみ
    // そのクラスのインスタンスとして比較
    case let (ltype, rtype) where ltype == rtype:
      if ltype == MySubclass.self {
        return lobj as! MySubclass == robj as! MySubclass
      } else {
        return lobj == robj
      }
    // クラスが一致しない場合はfalse
    default:
      return false
    }
  }
}
```

これで`MyWrapper`の`==`も期待通りに動作します。ただし、この場合、`MyClass`を継承する別のクラスや`MySubclass`をさらに継承するクラスを作ると、その都度この関数を修正する必要が生じます。即ち「`MyWrapper`の`.object`は`MyClass`と`MySubclass`のインスタンス以外ありえない」という状況でなければめんどくさいことになります。`open`なクラスでそれは…。

### 案②

`MyClass`や`MySubclass`を継承する別のクラスを作る予定(作られる可能性)があるのであれば、作る人間にその責任を負わせることにしましょう。つまり`MyWrapper`の`==`は最初の`mywrapper-eq.swift`のままうまくいくように、今度は`MyClass`や`MySubclass`のほうを弄ります。大改修(おおげさ)になるので、いっぺんにソース全体をお見せします。

```swift:myclass-mysubclass-mywrapper-revised.swift
open class MyClass: Equatable {
  public var a:Int
  public init(a:Int) { self.a = a }
  public func isEqual(to another:MyClass) -> Bool {
    return self.a == another.a
  }
  public static func ==(lhs:MyClass, rhs:MyClass) -> Bool {
    return lhs.isEqual(to:rhs) && rhs.isEqual(to:lhs)
  }
}

open class MySubclass: MyClass {
  public var b: Int
  public init(a:Int, b:Int) { self.b = b; super.init(a:a) }
  public override func isEqual(to another:MyClass) -> Bool {
    guard case let anotherInstance as MySubclass = another else {
      return false
    }
    return super.isEqual(to:another) && self.b == anotherInstance.b
  }
}

public struct MyWrapper: Equatable {
  public var object: MyClass
  public init(object:MyClass) { self.object = object }
  public static func ==(lhs:MyWrapper, rhs:MyWrapper) -> Bool {
    return lhs.object == rhs.object
  }
}
```

いろいろ変わっていますが、変更点のキモとしては、`MyClass`に`isEqual(to:)`というインスタンスメソッドを追加し、`==`からそのメソッドを呼ぶようにしたことです。そして、`MySubclass`には最早`==`というstatic funcはありません。その代わりに、`isEqual(to:)`をオーバライドしています。そして`MyWrapper`の`==`内では予定通り`return lhs.object == rhs.object`しかありません。なぜ、これでうまくいくのでしょうか？
それは、次のようなコードを実行してみるとよく分かるかと思います。

```swift
class A {
  func f() { print("I am A.") }
}
class B: A {
  override func f() { print("I am B.") }
}
let object: A = B()
(object as A).f()
```

なんと表示されましたか？
`let object: A = B()`と`object`の型は`A`であると宣言し、`f()`というインスタンスメソッドを実行する時にも`(object as A).f()`と`object`は`A`だと散々言い聞かせたにもかかわらず、表示されたのは無情にも
**`I am B.`**
もちろんオブジェクト指向としては至極当然の動作です。`object`は`B()`と生成されているので、`B`のインスタンスです。`(object as A).f()`としたところで、実行時に`B`のインスタンスメソッドが呼び出されることになります。

案②ではこの動作を利用しています。`MyWrapper`における`lhs.object == rhs.object`では`MyClass`のstatic funcである`==`が呼び出されるのは変更前と変わりませんが、その中では`isEqual(to:)`というインスタンスメソッドが呼び出されています。となると、`I am B.`の時と同じように、`lhs`(または`rhs`)が`MyClass`として渡されたとしてもそれが`MySubclass`のインスタンスであれば、ちゃんと`MySubclass`の`isEqual(to:)`が呼び出されるのです。仮に`MyClass`に別のサブクラスを作成したとしても、そのクラスの`isEqual(to:)`だけ実装すれば問題は生じません[^yetAnotherSubclass]。
この実装では、`Equatble`におけるSymmetry[^symmetry]を保証するために、`MyClass`の`==`が`return lhs.isEqual(to:rhs) && rhs.isEqual(to:lhs)`と冗長になってしまっているところが玉に瑕。ただ、例えば、仕様上`MyClass`と`MySubclass`の`.a`の値は必ず異なるといった場合(`.a`がインスタンスごとに割り振るIDであるなど)であれば、`return lhs.isEqual(to:rhs)`だけで済むこともあるはずです。

[^yetAnotherSubclass]: その別のサブクラスでも`.a`の値さえ同じなら同一とみなすのであれば、改めて実装する必要さえありません。自動的に`MyClass`の`isEqual(to:)`が呼び出されます。
[^symmetry]: 対称性。`a == b ⇔ b == a`というやつ。

### 案③

本当にそれはクラスである必要がありますか？
Swiftでは`struct`もいい働きをしますよ？そして折角のProtocolという仕組みです。さらにGenericsなんていうものもあります。クラスにこだわる必要もないのではないですか？
まだ"point of no return"を超えていないのであれば設計から見直しませんか？
こんなコードはいかがですか？

```swift:mystructures.swift
public protocol MyProtocol: Equatable {
  var a: Int { get }
}

public struct MyFirstStructure: MyProtocol {
  public var a: Int
  public init(a:Int) { self.a = a }
  public static func ==(lhs:MyFirstStructure, rhs:MyFirstStructure) -> Bool {
    return lhs.a == rhs.a
  }
}

public struct MySecondStructure: MyProtocol {
  public var a: Int
  public var b: Int
  public init(a:Int, b:Int) { self.a = a; self.b = b }
  public static func ==(lhs:MySecondStructure, rhs:MySecondStructure) -> Bool {
    return lhs.a == rhs.a && lhs.b == rhs.b
  }
}

public struct MyWrapper<Instance: MyProtocol>: Equatable {
  public var instance: Instance
  public init(instance:Instance) { self.instance = instance }
  public static func ==(lhs:MyWrapper<Instance>, rhs:MyWrapper<Instance>) -> Bool {
    return lhs.instance == rhs.instance
  }
}
```

これで済む場合も少なからずあるのではないでしょうか？ただ、もちろん、この場合は`MyFirstStructure`と`MySecondStructure`には継承関係がないので、それぞれのインスタンスを直接比較するにはまためんどくさいコードを書かないといけません。一方、最初から同一のクラスでしか比較をしないような設計なら、思い切って`struct`を使うのも手です。

### その他の案
コメント欄を参照(他力本願)

# おわりに
Swiftの教科書(なんてものがあれば)のProtocolの項で最初に出てきそうな`Equatable`ですが、単純な実装では場合によってバグを生み出すことがあるかもしれないことは意識しておかないといけないようです。


-----