【Swift】any, someとは一体何か

protocolは準拠していれば、たとえ型が違くてもprotocolに準拠した処理を行えるといった点で便利な反面、
- protocolに準拠していればどんな値でも代入可能になってしまう
- protocolへの準拠が非効率的なされることで、メモリを余分に消費してしまう

といったデメリットも存在します。そうしたデメリットを解消するためにSwiftに新しく追加されたのが、`any`や`some`といった文法です。
ここでは、それぞれの特徴や使い分けについて紹介していきます。
## anyの使い道
### anyを使うべき場合
#### 1. 型定義としてprotocolを用いる場合
例えば、以下のようなコードの場合を考えます。
```swift
protocol Food {
    var name: String { get }
    var weight: Int { get }
}

struct Vegetable: Food {
    var name: String
    var weight: Int
}

private let tomato: any Food = Vegetable(name: "Tomato",
                                         weight: 100)
```
このように型定義としてprotocolを用いる場合、`any`を明示的につけることで、ある変数にprotocolへ準拠した値が入ることを知らせることができます。
(この場合は`any`がついてなくてもコンパイルは通りますが、以下のように`associatedtype`があった場合、エラーとなります。)
```swift
protocol Food {
    associatedtype From
    var name: String { get }
    var weight: Int { get }
    var from: From { get }
}

struct Vegetable: Food {
    var name: String
    var weight: Int
    var from: String
}
// コンパイルエラー: Use of protocol 'Food' as a type must be written 'any Food'
private let tomato: Food = Vegetable(name: "Tomato",
                                     weight: 100,
                                     from: "Tokyo")
```
#### 2. 複数の型に対応したメソッドを定義する時
protocolに準拠している型が複数存在する場合、それらに共通して使えるメソッドを作りたい場合があります。
その場合、`any`を使うことでそのメソッドを定義できます。
例えば、以下のように合計値を出すメソッドを`Collection`に準拠するどのプロトコルにも使用したいと言う場合です。
```swift
func sum(data: any Collection<Int>) -> Int {
    return data.reduce(0) {$0 + $1}
}

let dataA = Array(1...5)
let dataB = Set(1...5)
let dataC = dataA.reversed()
let data: [any Collection<Int>] = [dataA, dataB, dataC]
for datum in data {
    print(sum(data: datum)) // 15, 15, 15
}
```
`Collection`に準拠する場合には`reduce`が使えるので、引数の型指定の部分に`any`を付与することで望み通りの関数を作ることができます。
また`data`のように、同じprotocolに準拠した複数の型を同時に入れるArrayを作りたい時にも`any`を使うことでコンパイルさせることができます。

**余談**
`Collection`に関して言うと、`any Collection`に相当する構造体である`AnyCollection`がSwiftでは定義されています。
```swift
let data: [AnyCollection<Int>] = [AnyCollection(dataA), AnyCollection(dataB), AnyCollection(dataC)]
for datum in data {
    print(sum(data: datum)) // 15, 15, 15
}
```
こちらでも全く同じ結果を得ることができます。`AnyCollection`は`Sequence`にも準拠しているので`flatMap`など`any Collection`には使えないメソッドを使うことができます。
### associatedtypeについて
`associatedtype`の利点は、型の制約を受けずに処理を記述できる点です。
`From`は`String`や`Int`等、何でも入れることができます。
制約を設けたい場合には、以下のように記述すれば大丈夫です。
```swift
protocol Food {
    // 制約を追加
    associatedtype From: Prefecture
    var name: String { get }
    var weight: Int { get }
    var from: From { get }
}

struct Vegetable: Food {
    var name: String
    var weight: Int
    var from: Pref
}

protocol Prefecture {
    var name: String { get }
}

enum Pref: Prefecture {
    case tokyo
    case other

    var name: String {
        switch self {
            case .tokyo:
                return "Tokyo"
            case .other:
                return "Not Tokyo"
        }
    }
}
private let tomato: any Food = Vegetable(name: "Tomato",
                                         weight: 100,
                                         from: .tokyo)
// コンパイルエラー
private let carrot: any Food = Vegetable(name: "Tomato",
                                         weight: 100,
                                         from: "Osaka")
```
### anyの制限
また、`any`を使用していると、準拠しているものであれば何でも指定できてしまいます。
```swift
struct Meat: Food {
    var name: String
    var weight: Int
    var from: Pref
}
private let beef = Meat(name: "Beef",
                        weight: 100,
                        from: .other)
protocol Store {
    var items: [any Food] { get }
    func register()
}

struct VegetableStore: Store {
    var items: [any Food] = []

    func register() {
        items.forEach {
            print($0.name)
            print($0.from.name)
        }
    }
}
var vegetableStore = VegetableStore()
vegetableStore.items = [tomato, beef]
vegetableStore.register()
// Tomato
// Tokyo
// Beef
// Not Tokyo
```
例えば上のように`Vegetable`である`Food`に限定したい場合、以下のようにジェネリクスを指定してあげることで、使用できる型を狭めることができます。
```swift
protocol Store {
    associatedtype FoodType: Food
    var items: [FoodType] { get }
    func register()
}

struct VegetableStore<T: Food>: Store {
    var items: [T] = []

    func register() {
        items.forEach {
            print($0.name)
            print($0.from.name)
        }
    }
}

var vegetableStore = VegetableStore<Vegetable>()
// コンパイルエラー：Cannot convert value of type 'Meat' to expected element type 'Array<Vegetable>.ArrayLiteralElement' (aka 'Vegetable')
vegetableStore.items = [tomato, beef]
vegetableStore.register()
```
### primary associated value
また、特定の型のオブジェクトに関する処理を記述したい場合の便利な書き方として、primary associated valueが挙げられます。
以下のようにprotocol自体にジェネリクスを定義することで、関数の型制約の部分でジェネリクスにより型制限を行うことができます。
```swift
protocol Store<FoodType> {
    associatedtype FoodType: Food
    var items: [FoodType] { get }
    func register()
}

func buy(_ store: any Store<Vegetable>) -> Vegetable {
    return store.items[0]
}
```
## someの使い道
`some`も`any`と同じく、型定義としてprotocolを用いるときに使われますが、一番の大きな違いは、コンパイル後の挙動です。
`any`がprotocolに準拠しているものであればコンパイル後でも何の型でも入るのに対し、`some`は異なる型を入れようとするとコンパイルエラーになると言う点です。`some`の場合、Arrayの中の要素も全て型が揃っていないといけません。
```swift
let dataA = Array(1...5)
let dataB = Set(1...5)
let dataC = dataA.reversed()
let data: [any Collection<Int>] = [dataA,dataB,dataC]
let data2: [some Collection<Int>] = [dataA,dataB,dataC] // コンパイルエラー: Conflicting arguments to generic parameter 'τ_0_0' ('[Int]' vs. 'Set<Int>' vs. 'ReversedCollection<[Int]>')

var dataAAny: any Collection = dataA
var dataASome: some Collection = dataA
dataAAny = dataB
// コンパイルエラー：Cannot assign value of type 'Set<Int>' to type '[Int]
dataASome = dataB
```
また、型の制約としてprotocolを使う場合も、引数の型が一定になるという点で`any`よりも`some`の方が望ましいです。
```swift
// 修正
func buy(_ store: some Store<Vegetable>) -> Vegetable {
    return store.items[0]
}
```
このように、`some`を使うと型の制約が強くなります。そのため、より安全なコードを書くためには`any`を使えそうなところは極力`some`に置き換える、といった認識が大事でしょう。

**余談**
SwiftUIで`View`は`var body: some View`から構成されています。これはViewに準拠していればどのようなViewの組み合わせでもコンパイル可能であり、何か特定の型で返すよりも非常に柔軟性が高く、かつエラーも生じにくい仕組みを使っていると言えます。
## まとめ
- protocolは値の型定義や`associatedtype`の制約、値への代入の制約などに用いることができる
  - protocolを型定義や型の制約に用いる場合、`any`あるいは`some`を使用するべきである
- `associatedtype`によって、同一のプロトコルに基づく複数の型に共通する処理を記述できる
- primary associated typeによって、特定の型のみに絞った処理を記述できる
- `some`と`any`の一番の違いは、コンパイル時に型決定をするかしないか
  - `any`だとコンパイル後に別の型を代入したり、別の型からなるArrayを作成したりできるが、`some`はコンパイル時に型を決定し、同一の型の値しか代入、Arrayの作成を行えない
- `some`の方が型的に安全であるため、極力`some`を使うのが望ましい。
### 最後に
こちらは私が書籍で学んだ学習内容をアウトプットしたものです。
わかりにくい点、間違っている点等ございましたら是非ご指摘お願いいたします。