Swift の型システムが微妙な件

**まとめ: 型システムが微妙**


- `[Equatable]` が `Equatable` じゃない
    - `extension` で Protocol を実装するとき、型パラメータを特殊化できないのでこうなる
- Protocol が型パラメータをもてない
    - `associatedtype` を持てるけど、これはメンバーや変数の型に指定できないのでワークアラウンドが必要になる


# `[Equatable]` が `Equatable` じゃない

Swift では `==` で比較可能な型には [`Equatable`](http://swiftdoc.org/v3.0/protocol/Equatable/) という Protocol がついています。例えば、`Int` や `String`、`Bool` は `Equatable` を実装しているので、`==` によって比較可能です:

```swift:わかる
print(10 == 10)       // true
print("str" == "str") // true
print(true == true)   // true
```

この `Equatable` は `==` の実装を要求するので、次のように簡単に実装できます:

```swift:==を実装すれば、なんでもEquatable
struct MyEquatable: Equatable {
  let value: Int
}

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

print(MyEquatable(value: 10) == MyEquatable(value: 10)) // true
```

さて、`Equatable` の配列である `[Equatable]` も `==` によって比較可能です:

```swift:[Equatable]はEquatableなのか素晴らしいじゃないか
print([10] == [10]) // true
```

そのため、`[Equatable]` も `Equatable` であると思い込みがちですが、**実際はそうではありません**:

```swift:最後のはなんなんだ
// == を関数に包んでみました
func eq<E: Equatable>(_ lhs: E, _ rhs: E) -> Bool {
    return lhs == rhs
}

print(eq(10, 10))       // OK, Int is an Equatable
print(eq("str", "str")) // OK, String is an Equatable
print(eq(true, true))   // OK, Bool is an Equatable
print(eq([10], [10]))   // NG, argument type '[Int]' does not conform to expected type 'Equatable'
```


## なぜか

`[Equatable]` で `==` が使えるのは、`==` に専用のオーバーロードが定義されているからです（[該当部分](https://github.com/apple/swift/blob/1eb5648c4636e6ef45149a5ec0425b3496f0e15a/stdlib/public/core/Arrays.swift.gyb#L2061-L2077)）:

```swift:stdlib/public/core/Arrays.swift.gyb
public func == <Element : Equatable>(
  lhs: Array<Element>, rhs: Array<Element>
) -> Bool {
  let lhsCount = lhs.count
  if lhsCount != rhs.count {
    return false
  }

  // ...
```

それなのに、`[Equatable]` は `Equatable` を実装していないのです。

これは Swift 3.0 の機能ではできないためにこうなっています。現状の Swift では特殊化された型パラメータをもつ総称型に Protocol を実装させられないのです:

```swift:本当はこうしたい（が、できない）
extension Array<Element>: Equatable where Element = Equatable {
  func ==<Element: Equatable>(lhs: Array<Element>, rhs: Array<Element>) -> Boo
    // ...
  }
}
```

このような、特殊化された総称型のみ Protocol を実装したとみなすパターンは頻繁に必要になります。そのため、[例えば Haskell であれば](https://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude.html#t:Eq)、比較可能であることを示す `Eq` は、`Eq` の配列も `Eq` であるとしています:

```haskell:こうあるべき
(Eq a) => Eq [a]
```


# Protocol が型パラメータをもてない

Swift では、Protocol が型パラメータをもてません:

```swift:厳しい
protocol Foo<T> {} // error: protocols do not allow generic parameters; use associated types instead
```

その代わりに、`associatedtype` を使うと、実質的に型パラメータを持たせられます:

```swift:代わりがあるにはある
protocol Foo {
  associatedtype Value
}

class MyFoo<V>: Foo {
  typealias Value = V

  init(_ value: V) {}
}


let foo: MyFoo<Int> = MyFoo(10) // ok
```

ただし、この方法で Protocol を総称型にすると、次のように Protocol を変数の型に利用できなくなります:

```swift:ﾌｧｰｰwwww
let ng: Foo = MyFoo(10) // error: protocol 'Foo' can only be used as a generic constraint because it has Self or associated type requirements
```

対処法として「[`AnyPokemon`](http://qiita.com/S_Shimotori/items/458a50d8f8e54e39de59)」というワークアラウンドがよく知られています。このワークアラウンドの詳細は割愛しますが、かなり強引な手法で、複雑で余計なコードを多く必要とする問題が依然として残されています。


# 終わりに

Swift が Obj-C に比べれば優れた言語であるというのはわかります。ただし、少なくとも現状の Swift を嫌う気持ちは変わりません。

なお、Swift はまだ進化を続けている言語ですから、ここで挙げている問題は将来的に解決されるかもしれません。