[Swift5.7] ナウでヤングなジェネリクスを書く

Swift5.7ではジェネリクス関連のさまざまな機能が追加されます。特に`some`を用いた軽量なジェネリクス構文を用いることで、既存のコードの可読性を大きく向上させることができます。

Swift5.7はXcode14以降で利用可能になる予定です。

# 1. `<T: P>`を`some P`で書く

以下のようなジェネリック関数を考えます。ここではまず、型パラメータ`T`を宣言し、次にそれが`Numeric`に準拠していることを示します。さらに引数リストにおいて`value`の型を`T`と指定します。

```swift
func printDouble<T: Numeric>(_ value: T) {
    print(value * 2)
}
```

`some`を用いると、上記のコードを以下のように書くことができます。ここでは型パラメータの宣言は行われず、代わりに引数リストの`value`の型が`some Numeric`になりました。この`some Numeric`は「`Numeric`に準拠したある特定の型」と言った意味合いで、振る舞いは型パラメータを使った場合と全く同じです。

```swift
func printDouble(_ value: some Numeric) {
    print(value * 2)
}
```

残念ながら、`some P`が使えない場面もあります。例えば次のような状況です。ここでは型パラメータ`T`が2回使われています。

```Swift
func swapNumeric<T: Numeric>(_ lhs: inout T, rhs: inout T) {
    (lhs, rhs) = (rhs, lhs)   
}
```

このような状況では`some`を使うことはできません。引数リストの2つの`some`は同じ型とは見做されないからです。だから、`some`を使って`swapNumeric`を下のように書いた場合

```swift
func swapNumeric(_ lhs: inout some Numeric, rhs: inout some Numeric) {
    (lhs, rhs) = (rhs, lhs)
}
```

解釈はこうなってしまいます。

```swift
func swapNumeric<T: Numeric, U: Numeric>(_ lhs: inout T, rhs: inout U) {
    (lhs, rhs) = (rhs, lhs)   
}
```

一般的に、同じ型パラメータを2度以上用いるような関数は`some`を使って書くことができません。また、ジェネリック型の型パラメータにも`some`は使えません。

# 2. `<T: Sequence> where T.Element == Int`を短く書く

次のようなジェネリック関数を考えます。ここでは`Collection`に準拠した型パラメータ`T`が宣言され、さらに`where`節において`Element`が`Int`であることが示されています。

```swift
func sum<T: Collection>(_ values: T) -> Int where T.Element == Int {
    return values.reduce(0, +)   
}
```

Swift5.7では「`Element`が`Int`の`Collection`」を`Collection<Int>`と書けるようになりました。そこで次のように書き換えることができます。`where`節がすっきりなくなり、非常にきれいになりました。

```swift
func sum<T: Collection<Int>>(_ values: T) -> Int {
    return values.reduce(0, +)   
}
```

これはSwift5.7で追加された「主要関連型」という機能によるものです。これによってプロトコルの関連型のうち「特に重要なもの」をジェネリクスのように表記できるようになりました。

Swift5.7での`Collection`は次のように宣言されています。

```swift
protocol Collection<Element> {
    associatedtype Element
    // ...
}
```

このため、例えば`Collection`の別の関連型である`Index`を参照するには、引き続き`where`節を用いる必要があります。

```swift
func first<T: Collection>(_ values: T) -> [Int] where T.Index == Int {
    return values[0]
}
```

主要関連型はプロトコルごとに決まっていますが、標準ライブラリでは`Collection`の`Element`のような直感的なものが選ばれています。

# 3. `<T: Collection<Int>>`をさらに短く書く

先ほど`<T: Collection<Int>>`を用いたコードを紹介しましたが、実は`some`と組み合わせることによってさらに簡潔に書くことが可能です。

```swift
func sum(_ values: some Collection<Int>) -> Int {
    return values.reduce(0, +)   
}
```

# 4. `any`を使う

ジェネリクスとは少しずれますが、ついでなので覚えておきましょう。

今まで、「`Codable`に準拠しているならなんでもいい」というような型（存在型）を書くときには、単に`Codable`と書いていました。

```swift
func foo(_ value: Codable) {}
```

Swift 5.6からは新しく`any`というキーワードが追加され、この関数を次のように書くことが奨励されています。

```swift
func foo(_ value: any Codable) {}
```

この記法に既存の記法との動作の違いはありません。Swift 6以降では上のコードをコンパイルエラーとすることが予定されているので、積極的に`any`を付けましょう。

この`any`は主要関連型とも組み合わせることができます。

```swift
func takeAnyCollectionOfInt(_ values: any Collection<Int>) {
    print(values.map {$0 * 2})
}
```

注意が必要な点に、`AnyHashable`のような型消去ラッパーと呼ばれる型とは機能が異なる点があります。`AnyHashable`は`Hashable`に準拠していますが、`any Hashable`は準拠していません。このため、例えば`[any Hashable: Int]`は許されず、`[AnyHashable: Int]`とする必要があります。一般的に`any P`は`P`に準拠しません。

# 5. ジェネリック関数を呼び出すためなら`any`を使って良い

「`any P`は`P`に準拠していない」という点から、いくつかの制約がありました。

```swift
func sum(_ values: some Collection<Int>) -> Int {
    return values.reduce(0, +)   
}
func takeAnyCollectionOfInt(_ values: any Collection<Int>) {
    print(sum(values))    // `any P`は`P`に準拠していないため、コンパイルエラーになってしまう   
    print(sum(AnyCollection(values)))    // AnyCollectionで型を消去してから使う必要がある
}
```
Swift5.7でも`any P`は`P`に準拠していませんが、「ジェネリック関数を呼び出す」という場面であれば`any P`を使えるようになりました。これは「存在型の暗黙的開放」という機能によるもので、ジェネリック関数を呼び出す際に暗黙的に`any P`の実際の型の情報が取り出されるようになったことで実現しました。

```swift
func takeAnyCollectionOfInt(_ values: any Collection<Int>) {
    print(sum(values))    // Swift5.7ではコンパイルエラーにならない
}
```

この機能はエッジケースが多いため、依然細かい制約はあります。しかしこのようなケースではもう`AnyCollection`を使う必要はありません。

# まとめ

ナウでヤングなジェネリクスを書くコツは以下の4つです。

+ なるべく型パラメータを明示的に宣言せず、`some`を使う
+ 主要関連型を積極的に使う
+ 存在型の`any`を明示的に書く
+ `AnyP`などの型消去ラッパーを可能な場面では`any P`に置き換える

これらに従うことで可読性の高いジェネリクスを書くことができます。積極的に使っていきましょう。