
基本的な文法と知識さえあればアプリの開発はできるけれども、初心者にはやっぱり訳がわからないアレやコレが登場するのがSwift3。
そんなアレコレを、せめて「何となく分かった」というくらいにはしたい人(筆者含む)のためのシリーズ。今回は「ジェネリクス」のお話。
Contents
ジェネリクスの何がすごいのか
こういうのはやっぱりメリットがわからないと理解が進まないと思う。ジェネリクスってやつは使うとどんな良いことがあるのか。
例えば、キーと値を引数に指定してデータを登録するような関数「touroku」を作ろうとした場合。touroku(“田中”,12345)というふうに使うと「田中」というキーで「12345」というデータが保存される具合。
ざっくり書くとこんな感じになるのではないだろうか。
1 2 3 |
func touroku(key:String, value:Int) { // keyをキーにして、valueの値を登録する処理 } |
しかしこれだと大問題。valueがInt型でないと(当然だけど)エラーになってしまう。
1 2 3 4 5 6 7 8 9 10 |
func touroku(key:String, value:Int) { // keyをキーにして、valueの値を登録する処理 } // これはOK touroku(key: "田中", value: 12345) // 以下の2つはvalueがInt型でないためエラー touroku(key: "木村", value: "カエラ") touroku(key: "円周率", value: 3.1415) |
じゃあ汎用性を持たせてやるぜ!というノリで、valueがString型・Double型のバージョンを作ればとりあえずは解決する。
しかし似たような関数をたくさん作るとコードが肥大化するし、そもそも他の型が登場したらどうするんだという話になってしまう(BoolとかURLとか)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 名前が被るから変えた func tourokuInt(key:String, value:Int) { // keyをキーにして、valueの値を登録する処理 } // せっかくだからStringとDoubleのバージョンも作るぜ! func tourokuString(key:String, value:String) { // keyをキーにして、valueの値を登録する処理 } func tourokuDouble(key:String, value:Double) { // keyをキーにして、valueの値を登録する処理 } // これはOK tourokuInt(key: "田中", value: 12345) tourokuString(key: "木村", value: "カエラ") tourokuDouble(key: "円周率", value: 3.1415) // でも他の型が来たら詰む(これはエラー) tourokuString(key: "T-SQUARE", value: true) |
ここで登場するのがジェネリクス。試しに関数を「1つだけ」、以下のように作ってみる。するとあら不思議、valueがどんな型でもエラーにならないのだ。
1 2 3 4 5 6 7 8 9 10 |
// ジェネリクスを使った関数 func touroku<T>(key:String, value:T) { // keyをキーにして、valueの値を登録する処理 } // valueがどんな型でもOK touroku(key: "田中", value: 12345) touroku(key: "木村", value: "カエラ") touroku(key: "円周率", value: 3.1415) touroku(key: "T-SQUARE", value: true) |
ということで、超ざっくりジェネリクスのすごい点をまとめると
「同じ処理をいろいろな型についてやりたい時、コードが簡潔になるよ!あと汎用性が上がるよ!」
といった感じである。
ジェネリクスの基本的な使い方
基本の基本
基本は
- 「どんな型でも良いよ」を表す文字を、<>の中に書く
- 普段型の名前を書くのと同じように、1で決めた名前を型名に指定する
の2段階。
ちなみに「T」としている文字は、開発者が自由に決めて良い。「U」でも「V」でも良いし、複数文字の単語を使っても良い。
1 2 3 4 |
// どれも同じ func tourokuT<T>(key:String, value:T) {} func tourokuElement<Element>(key:String, value:Element) {} func tourokuHokkaido<Hokkaido>(key:String, value:Hokkaido) {} |
2つ以上の別々な型を使う
例えば、以下のような関数があるとする。
1 2 3 |
func useTwoValue<T>(x:T, y:T) { // 何らかの処理 } |
引数に指定するxとyは型の指定がないので、IntだろうがStringだろうが何を入れても良い。
しかし、両者ともに「T」という同じ型名を使用しているため、xとyは同じ型である必要がある。
1 2 3 4 5 6 7 8 9 10 |
func useTwoValue<T>(x:T, y:T) { // 何らかの処理 } // xとyの型が同じなのでOK useTwoValue(x: 123, y: 100) useTwoValue(x: true, y: false) // xとyの型が異なるためNG useTwoValue(x: "加藤", y: 123) |
今回のように「型は何でも良いし、2つの型が違っていても良い」という条件にしたい時は、以下の通り別々の文字を型名に指定する必要がある。
1 2 3 4 5 6 7 8 9 10 11 |
// TとU、2つの文字を使った func useTwoValue<T,U>(x:T, y:U) { // 何らかの処理 } // xとyの型が異なっていてもOK useTwoValue(x: "加藤", y: 123) // もちろんxとyの型が同じでもOK useTwoValue(x: 123, y: 100) useTwoValue(x: true, y: false) |
型について無法地帯にならないの?
実行時エラーにならないよう歯止めがかかっている
「どんな型でも良い」というと、思わぬ型が入って実行時エラーになったりして無法地帯になるのではないか?という疑問がよぎる。
しかしそのあたりはちゃんと制御してくれるのがSwiftさん。
例えば、ジェネリクスを使用して2つの値を比較する関数を以下のように作ってみる。しかし、このままだとコンパイルエラーとなって実行はできない。
1 2 3 4 5 6 7 8 9 10 |
// xとyを比較する処理(エラー) func compare<T>(x:T, y:T) { if x > y { print("大きいのは\(x)") } else if y > x { print("大きいのは\(y)") } else { print("どっちも同じ") } } |
エラーの内容はこう。
Binary operator ‘>’ cannot be applied to two ‘T’ operands
どんな型でも良いよーと指定した「T」型の値は、比較演算子を使って比較することができないよ、という内容だ。
確かに、例えばBool型やURL型など、全ての型が比較可能というわけではない。ということで、型の違いによって思わぬエラーを招かないようにSwift側で歯止めをかけているわけだ。
型にある程度の制限をかける
では、先述のような「ジェネリクスを使った、2つの引数を比較する処理」は実現不可能なのかというと、そうではない。
compare<T>の部分を、compare<T: Comparable>と修正する。すると先ほど出たエラーが消え、ちゃんと関数が使えるようになる。
1 2 3 4 5 6 7 8 9 10 |
// xとyを比較する処理 func compare<T: Comparable>(x:T, y:T) { if x > y { print("大きいのは\(x)") } else if y > x { print("大きいのは\(y)") } else { print("どっちも同じ") } } |
鍵は、修正した<T: Comparable>の部分。このように書くと「Tはどんな型でも良いけど、比較可能な型じゃなきゃダメだよ」という意味になる。
このComparableの部分(従わなければならないルール)を「プロトコル」というらしい。
この状態で実際に関数を使うと、IntやDouble・Stringの比較は問題なく行える。一方でComparableというルールに当てはまらないBool型やURL型では、コンパイルエラーとなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// xとyを比較する処理 func compare<T: Comparable>(x:T, y:T) { if x > y { print("大きいのは\(x)") } else if y > x { print("大きいのは\(y)") } else { print("どっちも同じ") } } // OK compare(x: 12, y: 31) // 大きいのは31 compare(x: 1.44, y: 5) // 大きいのは5.0 compare(x: "aaa", y: "cosmos") // 大きいのはcosmos compare(x: 100, y: 100.0) // どっちも同じ // 比較できないのでエラー compare(x: true, y: false) compare(x: URL(string:"https://picolica.com")!, y: URL(string:"https://picolica.com/2017/07/12/swift3-colorbarmaker-release/")!) |
プロトコルには他にも「Equatable」や「Hashable」など色々存在する。