型消去の使い方 in Swift
Swiftは非常に型が強くまたSelf
、associatedtype
を持つプロトコルは型宣言として使用することが出来ない。
例えばDictionary
型は、異なる型のキーを同時に取ることが出来ない。
// コンパイルエラー let desc1 = ["😵" : "emoji", 42 : "an Int"];
AnyHashable型
本来DictionaryのキーはHashable
さえ満たしていれば良いので、型が同一である必要はない。
そこで 型消去ラッパー (type-erased Wrapper) と呼ばれる AnyHashable型 が用意されている。
// OK let desc2 = [AnyHashable("🥴") : "emoji", AnyHashable(42) : "an Int"]; // OK AnyHashableは文字列リテラル、数字リテラルから変換可能 let desc3: [AnyHashable: String] = ["🥴" : "emoji", 42 : "an Int"];
型消去型ラッパーとボックス化するための型との違いは、 ある明確な主張を持っている ところにある。
例えば上記のAnyHashable
を生成するには、Hashable
プロトコルに準拠した値を渡す必要がある。
struct A {} // NG 型AがHashableを満たしていないため let a = AnyHashable(A())
struct A: Hashable {} // OK let a = AnyHashable(A()) let desc4: [AnyHashable: String] = ["🥴" : "emoji", 42 : "an Int", a : "an A()"];
また自身もHashable
プロトコルに準拠する。
つまりAnyHashable
は、Hashable
プロトコルに準拠している型である ということを宣言している。
AnySequence型
型消去ラッパー型の使い方は、異なる型を同一の型に変換するだけではない。 「あるコンテキストを持つ型 を受け取れる」という宣言としても使用することが出来る。
これはSwiftが Self
、associatedtype
を持つプロトコルは型宣言として利用できないための代替である。
// NG func test(seq: Sequence) { ... // 型制約としてはOK func test<T>(seq: T) where T: Sequence, T.Element == Character { ...
例えば、Character
のシーケンスを受け取り、あるトークンを返すという関数を考える。
// Array型として引数を受け取る func convertToken(from chars: Array<Character>) -> Token { ...
このとき関数を以下のように呼び出すと、呼び出すたびにArray
を生成するためO(N)のコストを必要とする 。
// 一度Arrayを生成する必要がある。 convertToken(from: Array("Hello World"));
そこでAnySequece
を使うことで、コストなく引き渡すことが出来るようになる。
func convertToken(from chars: AnySequence<Character>) -> Token { ...
// Stringから呼び出しても生成コスト0 convertToken(from: AnySequence("Hello")) // Array<Character>から呼び出しても生成コスト0 convertToken(from: AnySequence(Array("Hello")))
これは以下のようにジェネリクスを使うよりもリーダブルにもなる。
func convertToken<T>(from chars: T) -> Token where T: Sequence, T.Element == Character { ...
まとめ
型消去の使い方
- 異なる型を同一の型に変換できる
Self
、associatedtype
を持つProtocolの代わりに型宣言として使用できる- ジェネリクスよりもリーダブルに表せる