見出し画像

この世でいちばんわかりやすいiPhoneアプリ開発のおはなし【ジェネリクスの基本】

構造体の基本については、こちらのページで説明しています。関数の基本については、こちらのページで説明しています。
また、型キャストやIf-Letステートメントを知っておくと、このページの理解が深まります。

「映画を再生する関数」と「音楽を再生する関数」

ここでは、映画や音楽などのメディアを再生するプログラムを考えます。 映画と音楽をモデル化する構造体は、それぞれを以下のように定義できます。

struct Movie {
   let title: String
}
let starwars = Movie(title: "Star Wars")

上のコードは、「スターウォーズ」という映画を示すインスタンスを作成します。

struct Music {
   let title: String
}
let spaceOddity = Music(title: "Space Oddity")

上のコードは、「スペースオディティ」という音楽を示すインスタンスを作成します。

まず、映画を再生するための関数を定義しましょう。

func playMovie(_ content: Movie) {
   print("Enjoy \(content.title)!")
}

このplayMovie(_:)関数は、パラメータとしてMovie型のインスタンスを受け取ります。 下のコードは、playMovie(_:)関数を呼び出します。

playMovie(starwars)
// Prints "Enjoy Star Wars!"

続いて、音楽を再生するplayMusici(_:)関数を定義します。

func playMusic(_ content: Music) {
   print("Enjoy \(content.title)!")
}

このplayMusic(_:)関数は、パラメータとしてMusic型のインスタンスを受け取ります。

実装を見ると明らかなように、ここで定義した2つの関数のボディは全く同じです。 違いは、パラメータとして受け取るデータの型だけです。

どんな種類のメディアでも再生できる汎用的な関数を定義できれば、コードがより柔軟になります。

映画でも音楽でも再生できる関数

どんな型に対しても機能する汎用的な関数をジェネリック関数といいます。 ジェネリック関数ではIntやStringなどの実際の型名ではなく、「それを示すプレースホルダー」を記述します。

以下に、映画と音楽を再生できる汎用的なplayMedia(_:)関数を定義します。

func playMedia<Media> (_ content: Media) {
  // play media here
}

ジェネリックな再生関数には、関数名の後に「仮の型名」として角括弧付きの<Media>が続きます。 角括弧<>は、Mediaが関数の定義において「呼び出された時に具体的な型が決まる」ことをSwiftに伝えます。 上の例では、Mediaと記述されている部分です。 つまり、Mediaは単なるプレースホルダーにすぎず、実際のプログラムにそのような名前の型は存在しません。

ジェネリックなplayMedia(_:)関数のボディでは、メディアの種類に基づいて適切な方法で再生できます。

func playMedia<Media> (_ content: Media) {
   if let movie = content as? Movie {
       print("Watch \(movie.title)!")
   } else if let music = content as? Music {
       print("Listen \(music.title)!")
   } else {
       print("This content is not playable.")
   }
}

ジェネリック関数のボディでは、型のプレースホルダーが「どんな型であるべきか」について言及しません。 上の例では、Mediaが「実際にどんな型と置き換えられるか」は、関数が呼び出されるときに推論されます。

playMedia(starwars)
// Prints "Watch Star Wars!"

上のコードは、ジェネリックな再生関数で映画を再生します。

下のコードは、ジェネリックな再生関数で音楽を再生します。

playMedia(spaceOddity)
// Prints "Listen Space Oddity!"

このように、汎用的(ジェネリック) なコードは状況に応じて「あらゆる型」で柔軟に機能するので、再利用できます。 コードをジェネリックにすると、コードの重複を減らし、意図を明確にして、その記述を抽象化できます。

ジェネリクスの利点

ジェネリックなコードを記述できることは、Swiftの最も強力な機能のひとつであり、ジェネリクスとも呼ばれます。 実際に、Swift標準ライブラリの多くはジェネリックなコードで構築されています。

「映画と音楽を再生するジェネリック関数」におけるプレースホルダーのMediaを型パラメータといいます。 型パラメータは、「プレースホルダーとしての型名」を関数名の直後に<T>のように、角括弧の間に記述することによって指定できます。

指定した型パラメータは、関数が受け取る引数の型、返り値の型、または「関数ボディでの型アノテーション」として使用できます。 いずれの場合でも、関数が呼び出されるときに、型パラメータは「実際に使用する具体的な型」に置き換えられます。 「映画と音楽を再生できるジェネリック関数」におけるMediaは、最初に呼び出されたときはMovie型に、2回目はMusic型に置き換えられました。

型パラメータに指定される名前は大抵の場合、その意図が伝わるように説明するものになっています。 例えは、Dictionary<Key, Value>のKeyとValueやArray<Element>のElementなどです。 これらは、「ジェネリックな型や関数」と「そこで使用される型パラメータ」の関係を明確にしています。

ただし、型パラメータと「ジェネリックな型や関数」の関係に、それほど意味がないこともあります。 その場合は、T、Vなどの文字を付ける慣習があります。

型パラメータには常に、TやMediaのような「大文字から始まる名前」にすることで、値ではなく「型のプレースホルダー」であることを明確にします。

この記事が気に入ったらサポートをしてみませんか?