だいたい同じことができる(ような気がする)別々の2つの言語機能についてよくわかっていないので書く。
// 筆者は型初心者。
型パラメータ
trait
には型パラメータを設けることができる。いわゆるジェネリクスというやつである。
trait Plus[A] { def plus(a2: A): A }
extend
することで具体的な型のインスタンスを得ることができる:
class p1(a1: Int) extends Plus[Int] { def plus(a2: Int): Int = a1+a2 } new p1(3).plus(4) // => 7
せやなという感じである。
抽象型
ところで,抽象型を使っても同じようなことができる。
trait Plus2 { type A def plus(a2: A): A }
こちらもインスタンス化してみよう:
class p2(a1: Int) extends Plus2 { type A = Int def plus(a2: A): A = a1+a2 } new p2(3).plus(4) // => 7
こちらもせやなという感じである。
違い 何
だいたい同じようなことができるのだが,型パラメータはupper/lower boundを指定できる:
trait Doubler[A <: Seq[Int]] { // Seq[Int]の子じゃないと受け付けないよ def double: A } class d(lis: List[Int]) extends Doubler[List[Int]] { def double: List[Int] = lis.map(_*2) } new d(List(1,2,3)).double // => List(2,4,6)
ところが抽象型でも同じことができる。
trait Doubler2 { type A <: Seq[Int] def double: A } class d2(lis: List[Int]) extends Doubler2 { type A = List[Int] def double: List[Int] = lis.map(_*2) } new d2(List(1,2,3)).double
全然違いがわからなくなってしまった。ここで答え合わせをしよう。
Stack Overflow先生に教えてもらう
雑に検索したら一発で出てきた。
要点は以下の通り。
- 「フィールド」「メソッド」「型」の抽象化にあたって,「パラメータで渡す」「抽象メンバにする」という2つのアプローチが考えられる
- Javaは型とフィールドをパラメータで渡せるようにし,メソッドは抽象メンバとして実装できるようにした
- Scalaはどちらでも良いように設計したので,パラメータにもできるし,抽象メンバにもできるようになっている
- パラメータ化は,難しいことをやろうとすると爆発的にパラメータ数が増加し,制御が難しくなるという問題を抱えている
- 抽象メンバではこの問題は起こりにくい
そういえばパラメータではなくメンバとして渡していく,というのはCake Patternにも共通していて面白いと思う。
そして,型パラメータはクライアントコードからは見えなくなるという点も違うようだ。抽象型は継承した先で参照・上書きできるが,型パラメータは隠蔽されて見えなくなってしまう。また型パラメータは使う時に名前が見えなくなる。
// 型パラメータ版 // library code trait Animal[A] { def eat(something: A): Unit } class Grass {} class Cow extends Animal[Grass] { def eat(g: Grass): Unit = { println("moo") } } // client code object BlackCow extends Cow { // Aはもう見えない!! override def eat(g: Grass): Unit = { println("moomoo") } } BlackCow.eat(new Grass)
// 抽象型版 // library code trait Animal { type Food def eat(something: Food): Unit } class Grass {} class Cow extends Animal { type Food <: Grass // ここでUpper boundを指定しておくと... def eat(g: Food): Unit = { println("moo") } } // client code class BlackGrass extends Grass {} object BlackCow extends Cow { override type Food = BlackGrass // 後から上書きできる override def eat(g: Food): Unit = { println("moomoo") } } BlackCow.eat(new BlackGrass)
全称型と存在型
ところで型パラメータと抽象型は全称型と存在型とに対応しているらしい(cf. p.33)。
www.slideshare.net
んーわからん。
- 型パラメータ
A
は全ての型A
についてモジュール内の関数に型が付く - 抽象型
A
はある型A
についてモジュール内の関数に型が付く
?????
abstract class JSON1[A] { def toJSON(x: A): String } class ConcreteJSON1[A] extends JSON1[A] { def toJSON(x: A): String = { // 全ての型を相手にしなければならない?? "" } } abstract class JSON2 { type X def toJSON(x: X): String } class ConcreteJSON2 extends JSON2 { type X = Int // ここでIntに絞れるということが言いたい?? def toJSON(x: X): String = x.toString() } // ところで class ConcreteJSON1Int extends JSON1[Int] { // 普通にここで絞れるじゃん def toJSON(x: Int): String = x.toString() }
forSome
なにもわからない。
おしまい