Make Standard Library Index Types Hashable
01 何が問題だったのか
Swift 4 でキーパス式(key-path expression)にサブスクリプトを含められるようになり、コレクションなどの特定の位置を参照できるようになりました。ただしこのサブスクリプトは、パラメータの型が Hashable であるときにしかキーパス中では使えません。
配列の添字型は Int なので、Int は Hashable に適合しており、Array をサブスクリプト付きキーパスで扱うことができます。
let numbers = [10, 20, 30, 40, 50]
let firstValue = \[Int].[0]
print(numbers[keyPath: firstValue]) // 10
一方で、String の添字型である String.Index をはじめ、標準ライブラリの多くの添字型は Hashable に適合していませんでした。そのため、String など独自の添字型を持つコレクションに対しては、同じようにサブスクリプトをキーパスに含めるとエラーになってしまいます。
let string = "Helloooo!"
let firstChar = \String.[string.startIndex]
// error: subscript index of type 'String.Index' in a key path must be Hashable
その結果、「キーパス中のサブスクリプトが使える型」と「使えない型」がコレクションごとにバラバラになり、キーパスの表現力を十分に活かせない状態になっていました。
02 どのように解決されるのか
標準ライブラリの添字型に Hashable 適合を追加し、[Int] でも String でも、その他の標準コレクションでも、サブスクリプト付きキーパスを同じように使えるようにします。
let string = "Helloooo!"
let firstChar = \String.[string.startIndex]
print(string[keyPath: firstChar]) // "H"
対象となる添字型
標準ライブラリの添字型は、内部に単純な値(オフセットなど)を持つ「単純な添字型」と、他の添字型を包み込む「ラップする添字型」の2種類に分けられます。本提案では、実装が単純な前者にまず Hashable 適合を追加します。
単純な添字型(本提案で Hashable を追加するもの):
Int(既にHashable)Dictionary.IndexSet.IndexString.Index
ラップする添字型(Hashable 適合は conditional conformance の実装完了を待ってから追加されるもの):
ClosedRangeIndexFlattenCollectionIndexLazyDropWhileIndexLazyFilterIndexLazyPrefixWhileIndexReversedIndex
これらのラップ型は、包んでいる内側の添字型が Hashable であるときに限って Hashable にしたいため、条件付き適合(conditional conformance)が必要で、その機能が揃ってから対応されます。
なお、任意の添字型を実行時に型消去する AnyIndex は、Hashable でない型を包む可能性があるため、本提案でも対象外です。
Collection プロトコルへの影響
この変更はあくまで標準ライブラリの添字型に適合を追加するだけの追加的な変更です。Collection プロトコルが添字型に要求する条件を変えるものではないため、自前のコレクションや添字型を定義している既存コードに影響はありません。