search
LoginSignup
12

More than 5 years have passed since last update.

posted at

updated at

Organization

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

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

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

[Equatable]Equatable じゃない

Swift では == で比較可能な型には Equatable という Protocol がついています。例えば、IntStringBoolEquatable を実装しているので、== によって比較可能です:

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

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

==を実装すれば、なんでも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]== によって比較可能です:

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

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

最後のはなんなんだ
// == を関数に包んでみました
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]== が使えるのは、== に専用のオーバーロードが定義されているからです(該当部分):

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 を実装させられないのです:

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

このような、特殊化された総称型のみ Protocol を実装したとみなすパターンは頻繁に必要になります。そのため、例えば Haskell であれば、比較可能であることを示す Eq は、Eq の配列も Eq であるとしています:

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

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

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

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

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

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

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

  init(_ value: V) {}
}


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

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

ファーー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」というワークアラウンドがよく知られています。このワークアラウンドの詳細は割愛しますが、かなり強引な手法で、複雑で余計なコードを多く必要とする問題が依然として残されています。

終わりに

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
12