良い設計には良い抽象が必要ですが、抽象化にも手段が複数通り考えられる場合があります。それぞれの利点を知っておくことは重要なことです。
硬い感じで始めてしまいましたが、abstract type members についてざっくりと書きます。変な時間に寝てしまった結果変な時間に起きてしまったので、デレステのスタミナが回復するまでの暇つぶしをします。
結論からいうと abstract type members は実装詳細の隠蔽に使えます。もっというと、実装詳細の隠蔽を行っていることも隠蔽…とはいえませんが、こそこそとできます。どうこそこそなのかは後述。
シンプルな例。
trait Exists { type A }
type Forall[B] = Exists { type A = B }
どちらも何かしらの型があるのだなということは分かります。Exists はメンバに、Forall は型パラメタにそれが表れています。継承する場合。
trait Foo extends Exists
trait Bar[A] extends Forall[A]
Exists を継承する場合、Foo は type A について何も知る必要も、与える必要もありません。勿論、それが型であろうと、抽象メンバがあると instantiation はできないので、最終的には type A とは何か、を誰かが決めてやる必要があります。しかし、Exists を直接継承するクラスないしトレイトに、必ずしもその責務があるとは限りません。
Forall を継承する場合、Bar は Forall に型パラメタを与えてやる必要があります。Forall を継承するクラスないしトレイトが適切な型パラメタを知っているなら問題ないのですが、そうでない場合には型パラメタをたらいまわしする必要があります。おかげで Bar も型パラメタを持つ羽目になってしまいました。Bar が A にとって興味がない場合、これは大変だるい。Bar を利用する側もだるい。
結局のところ、abstract type member にすべきか、generics(と取り合えず呼びます)にすべきかは、利用者がその型について知りたいかそうでないか、という所に落ち着きます。C や C++ を使っている人なら、abstract type members は opaque pointer のようなもの、という一文で理解してもらえると思います。generics はあえて言うなら template 相当ですね。勿論これは例えで、実際には表現力が全然違います。
あとは以下のようなコードを見るのも分かりやすい気がする。こそこそする例。
// abstract type members の場合
val a: Exists { type A = Int } = ...
val b: Exists { type B = Double } = ...
val c: Exists { type C = String } = ...
// 素直に書ける
val eseq: Seq[Exists] = Seq(a, b, c)
// generics の場合
val a: Forall[Int] = ...
val b: Forall[Double] = ...
val c: Forall[String] = ...
// existential types(Java の wildcard)を使う必要がある
val fseq: Seq[Forall[_]] = Seq(a, b, c)
Exists の Seq は素直に書けますが、Forall の Seq は existential types を使う必要があってだるいですね。今回の Forall は Exists の type alias なので、実際には existential types を使わなくても型推論は通るのですが、type A <: Double with Int with String みたいな、どうしようもない制約がついて使い物にならなくなるので、結局 existential types が必要になります。
勿論欠点もあります。Forall なら、existential types で隠蔽したりしていない限りは、型パラメタに表れてるくらいならので、その型について知ることができます。Forall[Int] なら Int でしょうし Forall[Double] なら Double です。当たり前のことですね。
一方 Exists の場合は、その型について基本的には知ることができません。path-dependent types を使ってコードを書く際、それがなんの型であるか知る術はありません。実際には Exists { type A = Int } として instantiation されたものであったとしても、型レベルで Exists であることしか分かっていないコンテキストでは、A が Int であることを利用者が知ることはできません。Exists { type A = Int } みたいな場合はそりゃ Int って分かるんですけど。generics 素直に使えよという感じですね。
というわけで、利用者が型について知る必要があるなら abstract type members は適任ではありません。利用者が知るべき型は、そもそも実装詳細とは呼べないですね。abstract type members で隠蔽しちゃいけません。
まとめ。abstract type members は実装詳細の隠蔽に使えます。同様のことは generics でもできなくはないですが、実装詳細にとって必要な型が型パラメタに表れてくるので大変めんどくさいです。Java はこれができないので、めんどくさい気持ちになる場合が稀によくある。ただし実装詳細の型を利用者が知らないといけない場合には generics を使うべきです。が、実装詳細の型を利用者が知らないといけない時点で、それは本当に実装詳細なのか、とか色々と設計を疑うべきでしょう。
C や C++ を知っている人は、opaque pointer のように使えるものなんだなーと思ってれば、それで大体大丈夫です。おわり。
適当なライブラリから実際のコード例をひっぱってきたかったんですが、スタミナが回復したのでおしまいです、デレステして寝ます。とはいえこれでおしまいはあまりにもあんまりなので、後で思い出したら追記するかもしれません。
蛇足、型の小難しい話。abstract type members は ML の abstract types 相当ですし、Forall の例で Scala の言語機能である existential types を使ったことからも分かるように(Scala の言語機能ではなく、一般にいうところの)existential types です。
あとデレパ Co Mas 一発でフルコンできました。良かったですね。おやすみなさい。