皆、元気してた?久々の投稿をさせてもらうね。
今回から 応用編 に突入するよ!
今回のテーマは、 型パラメータ だ!
こんな感じで語っていくからね。
型パラメータとは
型パラメータってなんだろ? 意味分かんないぞ!
と思っていたら、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.t
は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)
}
ここではString
とInt
の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
で、以下のどちらかの制約になるんだ。
-
A
はB
のサブ型 -
A
はB
と同一型
上限境界を定義した新しいクラスを定義してみる。
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
で、以下のどちらかの制約になるんだ。
-
A
はB
のスーパ型 -
A
はB
と同一型
今度は、下限境界を定義した新しいクラスを定義してみる。
このクラス達を使ってみてみよう!
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の場合、String
がObject
のサブクラスだからといって、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
とする。
- その型のみを許容する。型パラメータで
それぞれ見てみましょう。
ここでは、型境界で登場したAnimal
、Person
、Student
クラスに再登場していもらいます。
共変
- 型の特化を許容する
サブクラスを許容します。制限を付けたいときは、下限境界が使えるぞ。
ソースを見てみよう!
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を今までよりももっと読めるようになるよ!
ところで応用編に突入してみたけど、今までのノリと変わってないだろ?
今回も
体で感じてくれたかな?