Java
Scala
オブジェクト指向
関数型言語
More than 5 years have passed since last update.

皆、元気してた?久々の投稿をさせてもらうね。

今回から 応用編 に突入するよ!

今回のテーマは、 型パラメータ だ!

こんな感じで語っていくからね。


型パラメータとは

型パラメータってなんだろ? 意味分かんないぞ!

と思っていたら、Javaで言う ジェネリックス(総称型) のことを、Scalaでは 型パラメータ と呼ぶみたいなんだ。

型自体をパラメータとし使うことができるんだ。

クラスの定義自体はジェネリックスにしておいて、インスタンス化するときに型を決定する。

なんてことができるようになるんだ。

例えば、JavaのArrayListの定義を抜粋するとこんな感じです。

public class ArrayList<E> {...}

<E>でジェネリックスだぜって言っておいて

実際に使うときは、

final List<String> arrays = new ArrayList<String>()

として、EだったところにStringとして、ArrayListのインスタンスを生成します。

これでこのListには、String型の要素しか入れれないぜ!てことができるんだ。

同じことを、もちろんScalaでもできるのです。


型パラメータのメリット

Scalaでの例を見る前に、ちょっとメリット的なものを。

僕は具体的な型パラメータを決めることを、 型縛り と呼ぶんだけど

型縛りできるおかげで


  • 違う型を使うとコンパイルエラーになる

  • キャストが必要なくなる

なんて恩恵を得られるようになるんだ。

例えばコレクション系のMapやSeqで型縛りすれば、 縛った型 の要素だけを入れるようにすることができる。

あと抽象度が高いモノが作れるんだ。

さっき見たJavaのArrayListがまさにそうで、ArrayList自身は何かの要素を入れれる箱として定義している。

箱に何を入れるかはインスタンス化する時に決めれる。


型パラメータを使ってみる

では、Scalaでの型パラメータを使ってみよう!

こんなの作ってみたぜ!

object TypeParamSample {

class TypeParam[T](val t: T) {
def get: T = this.t
}

def main(args: Array[String]): Unit = {
val stringTypeParam = new TypeParam[String]("test")
println(stringTypeParam.get)

val intTypeParam = new TypeParam[Int](1)
println(intTypeParam.get)
}

}

説明するぞ


型パラメータのクラスの定義

  class TypeParam[T](val t: T) {

def get: T = this.t
}

class TypeParam[T]で型パラメータを使うクラスを宣言したぞ。

Javaだと<...>なんだけど、Scalaだと[...]になるんだ。

何がTなんだ?ということで、インスタンス変数tの型を型パラメータで解決することにする。

def get: T = this.ttの値を返すメソッドです。


使う側

使う側はシンプルだよ!

  def main(args: Array[String]): Unit = {

val stringTypeParam = new TypeParam[String]("test")
println(stringTypeParam.get)

val intTypeParam = new TypeParam[Int](1)
println(intTypeParam.get)
}

ここではStringIntの2つの型で、TypeParamを使ってみました。


動かす

$ scalac TypeParamSample.scala

$ scala TypeParamSample
test
1

ちゃんとgetできているね!


型境界

次に 型境界 だ!

型境界とは型の範囲を決めることです。

ある程度ゆるい範囲を型パラメータとして使えるようになるのがメリットかな。

パラメータ化した型の範囲を定義します。

Scalaの型は、Anyを頂点として継承の階層構造となっているので、範囲を決めることができるのです。

Javaの<? extends A>と似ていますね。


  • 上限境界

  • 下限境界

があります。

実際に見て行く前に、以下の型を定義しておきます。

scala> class Animal

defined class Animal

scala> class Person extends Animal
defined class Person

scala> class Student extends Person
defined class Student


上限境界

スーパー型の上限を決めるのです。

A <: Bで、以下のどちらかの制約になるんだ。



  • ABのサブ型


  • ABと同一型

上限境界を定義した新しいクラスを定義してみる。

scala> class School[A <: Person]

defined class School

型パラメータを与えてインスタンスを作ってみよう!


サブ型の場合

scala> new School[Student]

res0: School[Student] = School@7de3ec13

問題なしですね。


同一型の場合

scala> new School[Person]

res1: School[Person] = School@35203079

これも問題なし。


境界外の型の場合

scala> new School[Animal]

<console>:10: error: type arguments [Animal] do not conform to class School's type parameter bounds [A <: Person]
val res2 =
^
<console>:11: error: type arguments [Animal] do not conform to class School's type parameter bounds [A <: Person]
new School[Animal]
^

Animal上限境界 を超えているので、エラーになりました!


下限境界

サブ型の下限を決めるのです。

まぁ上限の逆だよね。

A >: Bで、以下のどちらかの制約になるんだ。



  • ABのスーパ型


  • ABと同一型

今度は、下限境界を定義した新しいクラスを定義してみる。

このクラス達を使ってみてみよう!

scala> class School[A >: Person]

defined class School

下限境界でも、型パラメータを与えてインスタンスを作ってみよう!


スーパー型の場合

scala> new School[Animal]

res0: School[Animal] = School@4d832421

問題なしですね。


同一型の場合

scala> new School[Person]

res1: School[Person] = School@4d032cfc

これも問題なし。


境界外の型の場合

scala> new School[Student]

<console>:11: error: type arguments [Student] do not conform to class School's type parameter bounds [A >: Person]
val res2 =
^
<console>:12: error: type arguments [Student] do not conform to class School's type parameter bounds [A >: Person]
new School[Student]
^

Student下限境界 を超えているので、エラーになりました!


上限・下限境界

上限と下限を同時に設定することもできるんだ。

scala> class School[A >: Student <: Animal]

defined class School

これはAnimal Person StudentをOKにしたんだ。


変位指定アノテーション

これはJavaの世界ではない考え方です。

Javaの場合、StringObjectのサブクラスだからといって、List<String>List<Object>のサブクラスとは言えないですよね。

つまり

final List<String> list = new ArrayList<String>();

final List<Object> objList = list;

とすると、2行目がコンパイルエラーになります。

でもScalaではこれが可能となります!

そして、それを可能とするのが変位指定アノテーションです。

Scaladocを覗いてみます。ここではListクラスを取り上げます。

sealed abstract class List[+A] extends AbstractSeq[A] with LinearSeq[A] with Product with GenericTraversableTemplate[A, List] with LinearSeqOptimized[A, List[A]]

ちょっと例としてはかなり複雑ではあるですけどね。。。

なのでちょっと抜粋します。

class List[+A]

これでわかりやすくなったでしょ。削り過ぎとかは言わないでね!

このListクラスは型パラメータです。

そして型パラメータの+Aとなっています。

この中の+が、 変位指定アノテーションの世界 となります。

実は、+なしの場合も、立派に変位指定アノテーションなのです。

この変位指定アノテーションは、型の許容性を明示します。

型の方向性 と考えるとわかりやすいかも。


  • 汎化の方向性で許容するのか

  • 特化の方向性で許容するか

更に方向性を持たない場合の3つがあります。用語があるので纏めると


  • 共変


    • 型の特化を許容する。型パラメータで+Aとする。



  • 反変


    • 型の汎化を許容する。型パラメータで-Aとする。



  • 非変


    • その型のみを許容する。型パラメータでAとする。



それぞれ見てみましょう。

ここでは、型境界で登場したAnimalPersonStudentクラスに再登場していもらいます。


共変


  • 型の特化を許容する

サブクラスを許容します。制限を付けたいときは、下限境界が使えるぞ。

ソースを見てみよう!

scala> class School[+T]

defined class School

scala> def receive(args: School[Person]) = println("success!!!")
receive: (args: School[Person])Unit

ここでは共変なSchoolクラスを定義しています。

そしてSchool[Person]を受け取れる関数receiveを定義した。

ではこの関数を使ってみよう!

scala> receive(new School[Person])

success!!!

receiveの引数の型そのモノを渡しているのでOKですね。

scala> receive(new School[Student])

success!!!

共変なため、School[Person]のサブクラスのSchool[Student]もOKです。

scala> receive(new School[Animal])

<console>:12: error: type mismatch;
found : School[Animal]
required: School[Person]
receive(new School[Animal])
^

School[Person]のスーパークラスのSchool[Animal]の場合は、コンパイルエラーになりました。


反変


  • 型の汎化を許容する

スーパクラスを許容します。制限を付けたいときは、上限境界が使えるぞ。

同様に見てみます。

scala> class School[-T]

defined class School

scala> def receive(args: School[Person]) = println("success!!!")
receive: (args: School[Person])Unit

scala> receive(new School[Person])
success!!!

scala> receive(new School[Animal])
success!!!

scala> receive(new School[Student])
<console>:13: error: type mismatch;
found : School[Student]
required: School[Person]
receive(new School[Student])
^

今度はSchool[Student]の場合に、コンパイルエラーが発生しました。


非変


  • その型のみを許容する

こちらも同様に。

scala> class School[T]

defined class School

scala> def receive(args: School[Person]) = println("success!!!")
receive: (args: School[Person])Unit

scala> receive(new School[Person])
success!!!

scala> receive(new School[Animal])
<console>:12: error: type mismatch;
found : School[Animal]
required: School[Person]
Note: Animal >: Person, but class School is invariant in type T.
You may wish to define T as -T instead. (SLS 4.5)
receive(new School[Animal])
^

scala> receive(new School[Student])
<console>:13: error: type mismatch;
found : School[Student]
required: School[Person]
Note: Student <: Person, but class School is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
receive(new School[Student])
^

サブクラス・スーパークラスのどちらも許容しないことが確認できました。

ちなみに Javaの世界では、 全て非変 となっています。

ただし、 配列は共変 です。これは知らなかったな。。。


まとめ

今回は書き方もちょっと変えてみたぞ!内部リンクを付けてみたけどどうかな?必要ないって?

型の世界は奥が深いだろ。

でもこの辺がわかってくると、Scaladocを今までよりももっと読めるようになるよ!

ところで応用編に突入してみたけど、今までのノリと変わってないだろ?

今回も

体で感じてくれたかな?