[Swift]世界一わかりやすい講座シリーズ~ジェネリクスについて~

## 初めに
この記事では、Swiftにおける**ジェネリクス**について解説します。
難しい言葉は基本使わず、噛み砕いて説明しますので、この記事が読者の皆さんの理解促進につながれば幸いです。

## この記事の対象読者
・Swiftをある程度学習している。
・プロトコルについてざっくり理解している

## そもそもジェネリクスとは
ジェネリクスとは一言でいうと、**汎用的なプログラムを記述するための機能**のことです。
そこで汎用的なプログラムについて理解するために、まずは**非汎用的なプログラム**を見てみます。
以下は引数として渡された値が等しいかを判別するプログラムです。
```swift:
//引数がInt型の時
func chackInt(_ x: Int, _ y: Int) -> Bool {
    return x == y
}
chackInt(1, 1)

//引数がfloat型のとき
func checkFloat(_ x: Float, _ y: Float) -> Bool {
    return x == y
}
checkFloat(1.1, 1.1) // true

//引数がString型のとき
func checkString(_ x: String, _ y: String) -> Bool {
    return x == y
}
checkString("apple", "apple") // true
```
上記のプログラムでは行いたいことは**引数2つが同じかどうかの判定**ですが、**引数の型が異なるといちいち書き換える**必要が出てきます😭

これは正直結構めんどうなことですが、ジェネリクスを用いるとこのめんどくささを解消することができます。以下がジェネリクスを用いたプログラムです。
```swift: 
func isEqual<T : Equatable>(_ x: T, _ y: T) -> Bool {
    return x == y
}

isEqual("abc", "def") // false
isEqual(1.0, 3.3) // false
isEqual("apple", "apple") //true
```
上記のようにジェネリクスを用いると、コードを書く量を減らすことができます。

軽く解説すると、上記のプログラムの引数の型Tは、`<T : Equatable>`であり、これはEquatableプロトコルに準拠したあらゆる型ということで、StringやInt,Floatといった基本的な型は全てEquatableプロトコルに準拠しているため引数として使用できるということです。(ここは今は読み飛ばしても大丈夫です。)

このように、ジェネリクスの基本的なコンセプトとしては、**入力値の型も任意にすることによってプログラムの汎用性を高める**ということだけ頭に入れておいて欲しいです。

swiftでは、ジェネリクスは上記で使用した**ジェネリクス関数**と**ジェネリクス型**の2つが用意されています。今回の記事では目にする回数が多い、ジェネリクス関数について解説したいと思います。

## ジェネリクス関数について
ジェネリック関数は型引数(**型が特定のものに定まっていない引数**)を持つ関数のことです。(先程使用した汎用的な関数もジェネリクス関数です。)
ジェネリック関数は以下のように定義できます。
```swift: ジェネリック関数の定義
func 関数名<T>(引数: 引数の型) -> 戻り値の型{
    //処理
}

//ex
func identity<T>(_ x: T) -> T {
    return x
}
identity(1) // 1
identity("abc") // "abc"
```
ジェネリック関数は、型引数を関数定義の関数名の直後に追加することで定義できます。
ジェネリック関数を用いることで、exのように**引数の型が異なっていたとしてもいちいちプログラムを書き換える必要がなくなります。**

また、**型引数として受け取った型は、引数や戻り値、さらには関数内部でも使用できます。**

このようにジェネリック関数は、宣言時は型が定まっていません。
そのため、ジェネリック関数を使用するときは**特殊化**(型の決定)を行う必要があります。
特殊化の方法は、以下の二通りがあります。
・**引数からの型推論による特殊化**(ジェネリック関数の引数のうち少なくとも一つの型が型引数となっている時)
。**戻り値から型推論による特殊化**(ジェネリック関数の戻り値の型が型引数となっていてかつ戻り値の代入先の型が決まっている時)
それぞれ具体例を確認します。
```swift:
//引数からの型推論による特殊化、つまり引数で型引数を決定している
func sampleFunction<T>(_ argument: T) -> T {
    return argument
}

let int = sampleFunction(1) // 1
let string = sampleFunction("a") // "a"

//戻り値から型推論による特殊化、つまり戻り値で型引数を決定している
func someFunction<T>(_ any: Any) -> T? {
    return any as? T
}

let a: String? = someFunction("abc") // Optional("abc")
let b: Int? = someFunction(1) // Optional(1)
// let c = someFunction("abc") // Tが決定できずコンパイルエラー
```
このように、ジェネリクス関数を使用するときは必ず特殊化を行う必要があり、Tが決定できない場合はコンパイルエラーが起きます。


さてここまで読んで**ジェネリクス関数なんでも引数で入れれそうだ**と思った方も多いと思います。実際その通りですが、その反面**型引数はどのような型でも受け入れるため、型の性質を利用した記述ができないという欠点**😭がありました。
その欠点を解消するのが**型制約**という仕組みです。これは簡単にいうと、**自由度が高すぎる型引数に対して制約を設ける**ということです。型引数に対して必要十分な型制約を与えることで、**汎用性と型の性質を利用した具体的な処理とを両立することが可能**です。

型制約の種類には、以下の3種類があります。
・スーパークラスや準拠するプロトコルに対する制約
・連想型のスーパークラスや準拠するプロトコルに対する制約
・型同士の一致を要求する制約
それぞれ見ていきましょう。
### 1. スーパークラスや準拠するプロトコルに対する制約
型引数のスーパークラスに準拠するプロトコルに対しする制約を指定するには、型引数の後に:と続けてプロトコル名やスーパークラス名を指定します。具体例は以下の通りです。
```swift:
func checkEqual<T : Equatable>(_ x: T, _ y: T) -> Bool {
    return x == y
}

checkEqual("abc", "def") // false
```
上記の例では、引数で使用されている型引数TはEquatableプロトコルに準拠したものに限定しています。(プロトコルはルールブックのようなものだと思っておいてください。)このように限定することで、T型に対してEquatableプロトコルで定義されている`==`を使用できます。

簡単に言うと、**TはEquatableプロトコルに準拠してる物じゃなきゃダメ！**　と宣言しているのが`<T : Equatable>`です。

### 2. 連想県のスーパークラスや準拠するプロトコルに対する制約
型引数には、where節を追加できwhere節を用いることで**型引数に条件を追加できます**。具体的には、where節のなかで**条件を追加したいものの後に：をつけて準拠すべきプロトコルやスーパークラスを指定する**ことができます。
```swift:
func sortElement<T : Collection>(_ argument: T) -> [T.Element]
     where T.Element : Comparable {
    return argument.sorted()
}

sortElement([3, 2, 1]) // [1, 2, 3]
```
上記のジェネリック関数sortElement()では、型引数がまずCollectionプロトコルに従っており、尚且つwhere節の内部でT.Element(Tの要素のことです)がComparableプロトコルに従っていることを要求しています。このようにすることで、ジェネリック関数sortElement()の引数は**比較可能な要素をもったコレクションに限定される**ためソート処理を実装できるようになるのです。

イメージとしては、**where節の内部でさらに引数の方が従うべきルールを追加している**とすると理解しやすいと思います。

### 3. 型同士の一致を要求する制約
先ほど話たwhere節内部で**ルールを追加する以外**に**型同士の一致を要求する**ことができます。以下のプログラムを確認してみましょう。
```swift
func merge<T : Collection, U : Collection>(
    _ argument1: T, _ argument2: U) -> [T.Element]
    where T.Element == U.Element {

    return Array(argument1) + Array(argument2)
}

let array = [1, 2, 3] // [Int]型
let set = Set([1, 2, 3]) // Set<Int>型
let merge = merge(array, set) // [1, 2, 3, 2, 3, 1]
```
上記のジェネリック関数merge()ではまず、2つの引数TとUがCollectionプロトコルに従うことを要求しています。そしてwhere節の中でさらに、**T.ElementとU.Elementの型が一致することを求めている**ということです。このような制約を求めることで、配列や集合といったコレクションの種類は問わないもののその要素の型は地位していないといけないといったような**限定的な制限を設けることができます。** 上記のプログラムでも引数として[Int]型とSet<Int>型という、**異なる型(集合)でありながらも要素の型は一致している**プログラムを作成できました。

## (Extra)Any型との比較
ここからは少しExtraな内容になります。
先ほどまで話していた通り、ジェネリクスは汎用性のあるプログラムを作成するための機能と紹介してきましたが、実はもう１つ、ジェネリクスは**型安全性を保った上で汎用性の高いのあるプログラム**を作成することができます。
どういうことかというと、型引数は通常の型と同じ安全性を持っているということです。
```swift
func identity<T>(_ x: T) -> T {
    return x
}

identity(1) // 1
identity("abc") // "abc"
```
例えば上記のジェネリクス関数identify()では、引数でInt型を渡せば返り値もInt型、引数でString型を渡せば返り値もString型のように**引数と戻り値の型が同じである(Tの型である)ことが保証されています。**
わかりやすくいうと、**渡した型の情報がきちんと失われずに保持されている**という意味でこのプログラムは**型安全を保つことができる**といえます。

つまりジェネリクスは、**型安全性を保った上で汎用性の高いのあるプログラム**を提供できるということです。

さてここで型安全性を保った上で汎用性の高いのあるプログラムという話をしましたが、Swiftには**型安全性ではない汎用性の高いのあるプログラム**も存在します。それが**Any型による汎用化**です。いかに比較するプログラムを書いてみます。
```swift
// ジェネリクスを使った関数
func identityWithGeneric<T>(_ argument: T) -> T {
    return argument
}

let genericInt = identityWithGeneric(1) // Int型
let genericString = identityWithGeneric("abc") // String型

// Anyを使った関数
func identityWithAny(_ argument: Any) -> Any {
    return argument
}

let anyInt = identityWithAny(1) // Any型
let anyString = identityWithAny("abc") // Any型

if let int = anyInt as? Int {
    // ここでようやくInt型として扱えるようになる
    print("anyInt is \(int)")
} else {
    // Int型へのダウンキャストが失敗した場合を考慮する必要がある
    print("The type of anyInt is not Int")
}
```
上記のプログラムの関数はどちらも、引数で受け取った値をそのまま返却する関数です。identityWithAny関数とidentityWithGeneric関数はどちらも汎用性が高くなっており、**引数の型に応じてプログラムを書き換える必要はありません。**
ですが返り値を見てみると、ジェネリクスを使った関数は型引数によって型がきちんと変化しているのに対して、Anyを用いた関数では**返り値は必ずAny**となっています。
そのため引数を使用する際には`as?`を用いてダウンキャストして、**型をAnyからIntやStringに変更してから**ではないと使用することはできないのです。

つまりAnyを用いると、返り値も必ずAnyになるため**実際の方の情報が失われてしまっています。** そのため型安全性は保持されていない汎用的なプログラムということができるでしょう。

## まとめ
これまで説明してきた通り、ジェネリクス関数は使いこなせば無駄にコードを書く回数を減らすことができたりするためとても便利です。

ですが使いこなすには**プロトコルやスーパークラスの知識がないと意図せぬ動きをするので注意が必要**です。

ここまで読んでいただきありがとうございました🙇‍♀️
## 参考文献
[Swift実践入門](https://www.amazon.co.jp/%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%AC%AC3%E7%89%88-Swift%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80-%E7%9B%B4%E6%84%9F%E7%9A%84%E3%81%AA%E6%96%87%E6%B3%95%E3%81%A8%E5%AE%89%E5%85%A8%E6%80%A7%E3%82%92%E5%85%BC%E3%81%AD%E5%82%99%E3%81%88%E3%81%9F%E8%A8%80%E8%AA%9E-PRESS-plus%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA/dp/4297112132)