Hashable Conformance for Dictionary.Keys, CollectionOfOne and EmptyCollection
01 何が問題だったのか
標準ライブラリには、コレクションを扱うためのいくつかの補助的な型が用意されています。そのうち、以下の3つの型は Hashable に適合していませんでした。
Dictionary.Keys:Dictionaryのキーのビューを表す型CollectionOfOne: 要素をちょうど1つだけ持つコレクションEmptyCollection: 要素を1つも持たないコレクション
これらが Hashable に適合していないと、Set や Dictionary のキーに使えない、他の Hashable な型の Hashable 合成に含められない、といった制約があります。
特に Dictionary.Keys については不自然です。Dictionary のキーは必ず Hashable に適合することが型制約で保証されているため、そのキーの集まりを表す Dictionary.Keys も本来であれば常に Hashable であるべきです。
CollectionOfOne は Equatable にすら適合していませんでした。単一の要素しか持たない以上、等価性もハッシュ値も要素そのものから自然に定まるはずですが、その適合が欠けていたため、こうした小さなコレクションを比較したり集合に入れたりすることができませんでした。
EmptyCollection も、空のコレクションはすべて等価でハッシュ値も同一とみなすのが自然ですが、Hashable 適合が無いために同様に使えない場面がありました。
いずれも、標準ライブラリの他のコレクション型(Array、Set、Dictionary など)が Hashable に適合していることとの一貫性・完全性の観点からも、欠けていた適合と言えます。
02 どのように解決されるのか
Dictionary.Keys、CollectionOfOne、EmptyCollection に対して、標準ライブラリが Hashable(および必要に応じて Equatable)への適合を追加します。いずれの適合も Swift 6.4 以降で利用できます。
Dictionary.Keys
Dictionary.Keys は無条件で Hashable に適合するようになります。キーは必ず Hashable であるため、追加の制約は不要です。
let batch1 = ["apple": 1, "banana": 2, "cherry": 3, "date": 4]
let batch2 = ["date": 10, "banana": 20, "apple": 30, "cherry": 40]
let batch3 = ["mango": 5, "orange": 6, "papaya": 7]
let uniqueBatches = Set([batch1.keys, batch2.keys, batch3.keys])
// batch1.keys と batch2.keys は同じキー集合なので、uniqueBatches の要素数は 2
Dictionary のキーは反復順が保証されないため、Dictionary.Keys のハッシュ実装は順序に依存しない可換なアルゴリズム(各要素のハッシュ値の XOR)を用います。これにより、同じキーを含む2つの Dictionary.Keys は反復順に関わらず同じハッシュ値を持ちます。
CollectionOfOne
CollectionOfOne は、Element が Equatable に適合する場合に条件付きで Equatable に、Hashable に適合する場合に条件付きで Hashable に適合するようになります。等価性・ハッシュ値はどちらも含まれる唯一の要素から導出されます。
let a: CollectionOfOne<Int> = CollectionOfOne(42)
let b: CollectionOfOne<Int> = CollectionOfOne(42)
a == b // true
var set: Set<CollectionOfOne<Int>> = []
set.insert(a)
set.contains(b) // true
EmptyCollection
EmptyCollection は無条件で Hashable に適合するようになります。空のコレクションはすべて等価であり、ハッシュ値もすべて同一となります(空の Set や Array と同じ規約で、内部的には 0 を混ぜ込む実装です)。
古い Swift バージョンでの利用
これらの適合を利用するには Swift 6.4 以降が必要です。それ以前の Swift にデプロイする場合は、利用側で retroactive conformance を宣言することで同等の適合を得られます。
#if swift(<6.4)
extension Dictionary.Keys: @retroactive Hashable {}
#endif
なお、追加された適合と重複する独自の適合がコード中に存在すると、Swift 6.4 以降ではコンパイラから警告が出るようになります。
Dictionary.Values について
類似の Dictionary.Values に対しては、今回は Hashable 適合を追加しません。Dictionary.Values は Dictionary.Keys と異なり重複要素を持ち得るため、正しい比較にはソートやカウントといった非自明なコストが必要です。今回の提案はあくまで「自明に抜けていた適合を埋める」ことが目的であり、新しいデータ構造やアルゴリズムの設計は対象外とされています。