ジェネリクスとは?
ジェネリクスとは型をパラメータとして受け取り、汎用的で型安全性が高いコードを実現するための機能です。
まあ実際に見てみないと分からないと思うので、早速サンプルコードをみていきましょう。
func Equal<T : Equatable>(_ x: T, _ y: T) -> Bool {
return x == y
}
//どの型でも値が等しいか判別できる。
Equal("abc", "abc") //true
Equal(1.05, 1.03)//false
Equal(true, true)//true
上記のコードを見ると、定義したEqual関数で複数の型に対して機能していることが確認できます。
これはT : Equatableという記述に秘密があります。この文字列は型引数というもので、引数の型を定義するものになっています。
今回で言うとTはEquatable型を設定されているため、そのTが引数として設定されたxとyもEquatable型ということになります。Equatable型にはString型やInt型など様々な型が準拠しているため、あらゆる型への==での評価を可能としました。
定義方法
//通常関数
func 関数名(引数名: 型) -> 戻り値の型 {
}
//ジェネリック関数
func 関数名<型引数>(引数名: 型引数) -> 戻り値の型 {
}
先ほどのコードと同じように、上記の記述でジェネリック関数が定義できます。
ちなみにジェネリック型というのは<型引数>のことを指し、またOptional型や配列のArray型などの<>内にプレースホルダー型(型引数の別名)を持つ型もジェネリック型ということになります。
特殊化
ジェネリック関数を呼び出したり、ジェネリック型を持つ型や構造体などをインスタンス化する際には型を確定させる必要があります。
このようにジェネリクスを利用して汎用的にしているものを、具体的に型を確定させることを特殊化と言います。
struct Container<Content> {
let content: Content
}
//型引数がStringに設定
let stringContainer = Container<String: "dog">
//型引数を型推論
let intContainer = Container(content: 3.14)
特殊化の方法は上記のように二つあり、型引数を明示する方法と型推論を利用する方法があります。
前者は引数の前に指定し、後者は引数を指定するだけでいいです。
型制約
型制約とはその名の通りジェネリック型に制約をつけることです。この記事でも先ほど使用しました。
func 関数名<型引数 : プロトコル名やスーパークラス名>(引数) {
}
定義方法は簡単で、上記のように型引数の後に:(コロン)プロトコル名やスーパークラス名を記述すればそれに準じた制約が可能になります。
連想型のスーパークラスやプロトコルに対する制約
通常の型制約で、あるプロトコルやスーパークラスに準じた型を定義したのちに、さらに他のプロトコルなどで制約したいときはwhere文を使い下記のように定義することができます。
func 関数名<型引数 : プロトコル>(引数) -> 戻り値の型
were 連想型 :プロトコルやスーパークラス {
}
下記ではこれのサンプルコードを記載します。
func sorted<U : Collection>(_ argument: T) -> [T.Element]
where T.Element : Comparable {
return argument.sorted()
}
sorted([5,4,1]) //[1,4,5]
上記のコードのようにCollectionプロトコルとComparableプロトコルで制約することで比較可能な列挙が実現できました。
Any型との差異
//ジェネリック型
func something<T>(_ argument: T) -> T {
return argument
}
let String = something("Test") //String型
let Double = something(3.14) //Double型
//Any型
func anything(_ argument: Any) -> Any {
return argument
}
let anyInt = anything(3)//Any型
let anyString = anything("cat")//Any型
ジェネリック型について考えると、Any型との差異がわかりづらいと思ったのでここに違いを記しておきます。
ジェネリック型は特殊化ができるため、関数実行時やインスタンス化時に型が確定し、型安全性が保証されます。
その一方Any型は全ての値がAny型として定義されてしまし、型安全性が保証されません。
そのため汎用的なコードを求める場合は、特別な理由がない限りジェネリック型を利用しましょう。