【Swift】ジェネリクスについて

#ジェネリクス とは
ジェネリクスとは、**型をパラメータとして受け取ることで
汎用的なプログラムを記述するための機能です。**

Swiftではジェネリクスは、**ジェネリック関数**と**ジェネリック型**として提供されています。

##汎用的なプログラム

ジェネリクスの利便性を知るために、ジェネリクスを使用しない例を記載します。

次のサンプルコードですが、
引数にもらった２つの値の同値性を確認しその結果を返す関数を定義しました。

```swift

func isEqual(_ x: Int, _ y: Int) -> Bool {
    x == y
}

isEqual(1, 1)   // true
```

これがジェネリクスを使用しないで定義した場合の関数になります。

任意の整数どうしを比較する汎用的な処理を行えますが、
**これはInt型の整数どうしにおいてのみになります。**

他にもString型やFloat型の比較をしたい場合は、
型ごとに同じ関数をオーバーロードする必要があります。

```swift

func isEqual(_ x: Int, _ y: Int) -> Bool {
    x == y
}

func isEqual(_ x: String, _ y: String) -> Bool {
    x == y
}

func isEqual(_ x: Float, _ y: Float) -> Bool {
    x == y
}

isEqual(1, 1)   // true
isEqual("abc", "abc")   // true
isEqual(1.11, 1.12)   // false
```

しかし、ジェネリクスを利用すれば、
**複数の型の間においても汎用的な処理を実装することができます。**

次のサンプルコードのようになります。

`<T: Equatable>`が意味するのは、
**Equatableプロトコルに準拠する全ての型**という意味になります。

String型やInt型、Float型は全てEquatableプロトコルに準拠しているので、
この関数を利用することができます。

`(_ x: T, _ y: T)`のように引数を同じT型で指定しているので、
引数を渡す際は同じ型を渡さなければいけません。

４つ目がString型とInt型を渡した場合になります。

```swift

func isEqual<T: Equatable>(_ x: T, _ y: T) -> Bool {
    x == y
}

isEqual(1, 1)   // true
isEqual("abc", "abc")   // true
isEqual(1.11, 1.12)   // false
isEqual("100", 100)   // コンパイルエラー
```

エラー内容：`Cannot convert value of type 'Int' to expected argument type 'String'`
和訳：タイプ 'Int'の値を期待される引数タイプ 'String'に変換できません

##定義方法

ジェネリック関数やジェネリック型を定義するには、
通常の定義に**型引数**を追加することで定義できます。

型引数は`< >`で囲み、複数ある場合は`,(カンマ)`で区切ります。

```swift

// 通常の関数の場合
func 関数名(引数名: 型)　-> 戻り値の型 {
   関数呼び出し時に実行される処理
}

// ジェネリック関数の場合
func 関数名<型引数>(引数名: 型引数) -> 戻り値の型 {
   関数呼び出し時に実行される処理
}
```

型引数として宣言された型は、
ジェネリック関数やジェネリック型の内部で**通常の型と同様に扱うことができます。**

また、ジェネリック関数の戻り値の型として利用することもできます。

次のサンプルコードでは、
第一引数TにはInt型が、第二引数UにはString型が入ります。
また、戻り値の型はString型になります。


```swift

func sampleMethod<T, U>(_ x: T, _ y: U) -> U {
    let a: T = x   // 型アノテーション
    let b = y   // 型推論
    let c = 1 as? T   // 型のキャスト
    print("aの値は\(a)、型は\(type(of: a))\nbの値は\(b)、型は\(type(of: b))\ncの型は\(type(of: c))")
    return y
}

sampleMethod(123, "abc")

実行結果
aの値は123、型はInt
bの値はabc、型はString
cの型はOptional<Int>
```
##特殊化方法

ジェネリック関数やジェネリック型の内部では型引数として型を抽象的に表現できますが、
実際にジェネリック関数を呼び出したり、ジェネリック型をインスタンス化する場合は、
型引数に具体的な値を指定する必要があります。

つまりどういうことかというと、
関数を定義する時は型引数をいろいろな処理で抽象的に使うことができますが、
関数呼び出し時や型のインスタンス化時に具体的な値を渡してね。ということです。

**ジェネリクスを使用して汎用的に定義されたものに対して、
具体的な型引数を与えて型を確立させることを特殊化と言います.**

特殊化の方法は大きく分けて２つあります。

次のサンプルコードでは、
**一つは< >内に型引数を明示する方法で、
もう一つは型推論によって型引数を推測させています。**

```swift

// Contentは型引数
struct Sample<T> {
    let a: T
}

//型引数がString型であることを明示する場合
let stringSample = Sample<String>(a: "abc")   // Sample<String>

//型引数を型推論する場合
let intSample = Sample(a: 123)   // Sample<Int>
```

Optional\<Wrapped>型やArray\<Element>型のWrapped型やElement型に、
Int型やString型などの具体的な型を当てはめて使用しますが、
これも特殊化の一つになります。

##仮型引数と実型引数
関数と同様にジェネリクスでも、ジェネリクスの定義時に使用する型引数と
ジェネリクスの特殊化時に指定する型引数を明示的に区別する場合は、

**前者を仮型引数、後者を実型引数と言います。**


##汎用性と型安全性の両立
ジェネリクスの良いところは汎用化だけではなく、
**静的型付けによる型安全性を保った上での汎用化になります。**

静的型付けとは、
実行前の段階でコンパイラにより型の安全性がチェックされる仕組みです。

事前にチェックされることにより、
実行中の型の不一致によるエラーが発生しなくなります。

**型引数はジェネリック関数やジェネリック型に保持され続けるため、
型引数として与えられた型は通常の型と同等の型安全性を持ちます。**

次のサンプルコードでは、
引数の型を型引数Tとして受け取り、戻り値を同じ型Tで返しています。

つまり、型引数としてInt型を渡せば戻り値の型はInt型に、
String型を渡せば戻り値の型はString型になります。

同じ型Tとして表現された、引数の型と戻り値の型が同じであることが保証されています。

```swift

func sample<T>(_ x: T) -> T {
    return x
}

let a = sample(123)   // Int型
let b = sample("abc")   // String型
```

##Any型との比較

実はジェネリクスだけではなく、Any型でも汎用化することができます。

**Any型は全ての型が暗黙的に準拠しているプロトコルです。
つまりどのような型でも表現することができます。**

ジェネリクスとAny型は何が違うの？といますと、
型安全性で大きな違いがあります。

ジェネリクスは**型安全性を保った上での汎用化**なのに対し、
Any型は**型安全ではない汎用化**になります。

次のサンプルコードでは、
`sampleGeneric(_:)`と`sampleAny(_:)`の両方ともが汎用的になっています。

なので、どちらもInt型やString型の引数を受け取り
受け取った値をそのままreturnで返しています。

しかし、ジェネリクスを使用した関数の戻り値の型が型引数に応じて変化するのに対し、
**Any型を使った関数の戻り値の型は常にAny型になってしまいます。**

これの何がダメなのかというと、
**Any型はダウンキャストをしないと本来の型として扱うことができないので、
実際にAny型の変数や定数を扱う時はダウンキャストする必要があります。**

```swift

// ジェネリクスを使った関数
func sampleGeneric<T>(_ x: T) -> T {
    return x
}

let a = sampleGeneric(123)   // Int型
let b = sampleGeneric("abc")   // String型

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

let c = sampleAny(123)   // Any型
let d = sampleAny("abc")   // Any型

// Int型としてダウンキャスト
if let int = c as? Int {
    // ここで初めてInt型として扱える
    print("c is \(int)")
} else {
    // Int型にダウンキャストできなかった場合を考慮する必要がある
    print("c is not Int")
}

実行結果
c is 123
```

以上がジェネリクスについての基本的な説明になります！

Any型とジェネリクスですが、
個人的にはジェネリクス一択かなと思います。

Anyの方がいいよ！という方がいらっしゃったら教えていただけると嬉しいです。

最後までご覧いただきありがとうございました。