Swift6.2のうれしい変更点 ~Add Collection conformances for enumerated()~

## はじめに
Swiftでは、言語仕様のアップデートが日々プロポーサルという形で提案・議論されています。
Swift6.2では、便利なアップデートが多く実施されるようです。

今回は、Swift6.2で導入される予定の`Add Collection conformances for enumerated()`というプロポーサルについてまとめます。
[プロポーサルのリンクはこちら](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0459-enumerated-collection.md)

## TL;DR
`enumerated()`の返り値である`EnumeratedSequence`が`Collection`に準拠するようになります
これによって以下の嬉しさがあります
- 特定の操作に対するパフォーマンスが大きく向上します
- SwiftUIの`List`や`ForEach`に対して、Arrayでラップせずに直接`EnumeratedSequence`を渡せるようになります。

## プロポーサルの概要
プロポーサルのIntroductionの章をみると、次のように書かれています。

> This proposal aims to fix the lack of Collection conformance of the sequence returned by enumerated(), preventing it from being used in a context that requires a Collection.

日本語に訳すと以下のようになるでしょうか。
> この提案は、enumerated() によって返されるシーケンスが Collection に準拠していないという問題を修正し、Collection を必要とするコンテキストで使用できるようにすることを目的としています。

つまり、`enumerated()`が返す`EmumeratedSequence`が`Collection`に準拠するということのようです。

## enumerated()とはなにか
まずは、今回の議論の対象である`enumerated()`について確認しましょう。
[公式ドキュメント](https://developer.apple.com/documentation/swift/array/enumerated())には以下のような説明がされています。

> Returns a sequence of pairs (n, x), where n represents a consecutive integer starting at zero and x represents an element of the sequence.

つまり、Arrayに対して、({0からの連番}, {Arrayの要素})をもつ`EnumeratedSequence`を返すメソッドということのようですね。

簡単なコードで確認してみましょう。

```Swift
let string = "Hello, World!"

for char in string.enumerated() {
    print(char)
}
```

上のコードの実行結果は、以下のようになります
```
(offset: 0, element: "H")
(offset: 1, element: "e")
(offset: 2, element: "l")
(offset: 3, element: "l")
(offset: 4, element: "o")
(offset: 5, element: ",")
(offset: 6, element: " ")
(offset: 7, element: "W")
(offset: 8, element: "o")
(offset: 9, element: "r")
(offset: 10, element: "l")
(offset: 11, element: "d")
(offset: 12, element: "!")
```

0からの連番には`offset`、Arrayの要素には`element`というラベルが与えられるようですね
繰り返し処理でindexを利用したい場合には非常に便利そうです

## enumerated()がCollectionに準拠するとどうなるのか
この`enumerated()`が`Collection`に準拠すると、どのような利点があるのでしょうか?
プロポーサルのMotivationの章には3点が紹介されていたので、順に説明します。

### EnumeratedSequenceが特定の操作を高速に実行できるようになる
プロポーサルでは次のような記述で解説されています。
> (1000..<2000).enumerated().dropFirst(500) becomes a constant time operation.

`EnumeratedSequence`が`Collection`に準拠していない場合、`dropFirst(500)`の実行時には、要素を１つずつ順にスキップする必要があり、O(n)の実行時間を要します。

`Collection`に準拠している場合、`dropFirst(500)`は内部的にindexが500の要素からのスライス処理によってO(1)の実行時間になります。

このようなポインタの移動や範囲指定によって達成できる操作のパフォーマンスが大きく向上されられるようです。


### reversed()の返り値がReversedColectionになる
プロポーサルでは次のような記述で解説されています。
> "abc".enumerated().reversed() will return a ReversedCollection rather than allocating a new array.

`Collection`への準拠のない`EnumeratedSequence`に対して`reverse()`を実行した場合、内部的にはすべての要素を走査してArrayに変換し、逆順に並べ直す必要があります。

`Collection`への準拠によって、`ReversedCollection`にラップして返すだけで、`reverse()`の処理を完了でき、O(1)の実行時間で処理を完了できます。
中間生成物であるArrayも不要です。

こちらも、パフォーマンスの大きな向上が期待できそうですね。

### SwiftUIのListやForEachでの利用がシンプルになる
プロポーサルでは次のような記述で解説されています。
> SwiftUI’s List and ForEach views will be able to directly take an enumerated collection as their data.

個人的には、一番うれしい利点です。
SwiftUIでは、`List`や`ForEach`に配列やコレクションを渡すことで要素を表示することができます。

しかし、`EnumeratedSequence`が`Collection`に準拠していないために、直接`List`やForEach`に`EnumeratedSequence`を直接渡すことができませんでした。
これを解決するためにArrayでラップするなどの工夫をしている方が多いのではないでしょうか。

例えば、以下のViewはコンパイルエラーになりません。
```Swift
struct ContentView: View {
    let strings = ["a", "b", "c"]
    
    var body: some View {
        VStack {
            ForEach(strings, id: \.self) { string in
                Text(string)
            }
        }
    }
}

```

次のViewはSwift6.2未満ではコンパイルエラーになります
```Swift
struct ContentView: View {
    let strings = ["a", "b", "c"]
    
    var body: some View {
        VStack {
            // strings.enumerated()はEnumeratedSequenceを返すが、Collectionに準拠していないのでコンパイル不可
            ForEach(strings.enumerated(), id: \.offset) { string in
                Text(string.element)
            }
        }
    }
}
```
エラーコードが以下になっており、`RandomAccessCollection`に準拠していないためにコンパイルできないことがわかります
> Generic struct 'ForEach' requires that 'EnumeratedSequence<[String]>' conform to 'RandomAccessCollection'

これを回避するためには、Arrayでラップするなどの工夫が必要です。

```Swift
struct ContentView: View {
    let strings = ["a", "b", "c"]
    
    var body: some View {
        VStack {
            // ArrayはCollectionに準拠しているのでコンパイルできる
            ForEach(Array(strings.enumerated()), id: \.self) { string in
                Text(string)
            }
        }
    }
}
```

Arrayは`Collection`に準拠しているため、`EnumeratedSequence`をラップすることで問題なくコンパイル可能です。

`EnumeratedSequence`が`Collection`に準拠することで、Arrayでラップする必要がなくなり、より直感的な記述ができるようになります。

## まとめ
今回は、Swift6.2で導入予定の`Add Collection conformances for enumerated()`というプロポーサルについてまとめてみました。

`enumerated()`の返り値である`EnumeratedSequence`が`Collection`に準拠することで
- これまで各要素を走査する必要があった処理の一部が、ポインタの移動や範囲指定によって効率的に実行できるようになる
- ReversedCollectionを利用することで、逆順の生成が効率的になる
- SwiftUIのListやForEachで、より直感的な記述ができるようになる

といった利点があることがわかりました！
今後indexを利用したViewの繰り返し処理でお世話になりそうな、ちょっと嬉しい変更ですね！

好評であればいくつかSwift6.2で採用されるプロポーサルをまとめようと思います。
ぜひいいねやストックなどよろしくお願いします！