web-dev-qa-db-ja.com

Scala:抽象型とジェネリック

私は読んでいたScalaのツアー:抽象型。抽象型を使用したほうがよいのはいつですか?

例えば、

abstract class Buffer {
  type T
  val element: T
}

むしろジェネリック、例えば、

abstract class Buffer[T] {
  val element: T
}
237
thatismatt

Scalaについて読んでいたときにも同じ質問がありました。

ジェネリックを使用する利点は、タイプのファミリーを作成することです。 Bufferをサブクラス化する必要はありません。Buffer[Any]Buffer[String]などを使用できます。

抽象型を使用すると、人々はサブクラスの作成を余儀なくされます。 AnyBufferStringBufferなどのクラスが必要です。

特定のニーズに適したものを決定する必要があります。

37

型パラメータと組み合わせて抽象型を使用して、カスタムテンプレートを確立できます。

3つの関連する特性を持つパターンを確立する必要があると仮定します。

trait AA[B,C]
trait BB[C,A]
trait CC[A,B]

型パラメーターで言及されている引数が、AA、BB、CCそのものであるように

何らかのコードが付属している場合があります。

trait AA[B<:BB[C,AA[B,C]],C<:CC[AA[B,C],B]]
trait BB[C<:CC[A,BB[C,A]],A<:AA[BB[C,A],C]]
trait CC[A<:AA[B,CC[A,B]],B<:BB[CC[A,B],A]]

型パラメーター結合のため、この単純な方法では機能しません。正しく継承するには共変にする必要があります

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]]
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]]
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]]

この1つのサンプルはコンパイルされますが、分散ルールに強い要件が設定され、場合によっては使用できません

trait AA[+B<:BB[C,AA[B,C]],+C<:CC[AA[B,C],B]] {
  def forth(x:B):C
  def back(x:C):B
}
trait BB[+C<:CC[A,BB[C,A]],+A<:AA[BB[C,A],C]] {
  def forth(x:C):A
  def back(x:A):C
}
trait CC[+A<:AA[B,CC[A,B]],+B<:BB[CC[A,B],A]] {
  def forth(x:A):B
  def back(x:B):A
}

コンパイラーは、多数の分散チェックエラーに反対します。

その場合、追加の特性ですべての型の要件を収集し、その上に他の特性をパラメータ化することができます

//one trait to rule them all
trait OO[O <: OO[O]] { this : O =>
  type A <: AA[O]
  type B <: BB[O]
  type C <: CC[O]
}
trait AA[O <: OO[O]] { this : O#A =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:B):C
  def right(r:C):B = r.left(this)
  def join(l:B, r:C):A
  def double(l:B, r:C):A = this.join( l.join(r,this), r.join(this,l) )
}
trait BB[O <: OO[O]] { this : O#B =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:C):A
  def right(r:A):C = r.left(this)
  def join(l:C, r:A):B
  def double(l:C, r:A):B = this.join( l.join(r,this), r.join(this,l) )
}
trait CC[O <: OO[O]] { this : O#C =>
  type A = O#A
  type B = O#B
  type C = O#C
  def left(l:A):B
  def right(r:B):A = r.left(this)
  def join(l:A, r:B):C
  def double(l:A, r:B):C = this.join( l.join(r,this), r.join(this,l) )
}

これで、記述されたパターンの具体的な表現を記述し、すべてのクラスでleftメソッドとjoinメソッドを定義し、右とdoubleを無料で取得できます

class ReprO extends OO[ReprO] {
  override type A = ReprA
  override type B = ReprB
  override type C = ReprC
}
case class ReprA(data : Int) extends AA[ReprO] {
  override def left(l:B):C = ReprC(data - l.data)
  override def join(l:B, r:C) = ReprA(l.data + r.data)
}
case class ReprB(data : Int) extends BB[ReprO] {
  override def left(l:C):A = ReprA(data - l.data)
  override def join(l:C, r:A):B = ReprB(l.data + r.data)
}
case class ReprC(data : Int) extends CC[ReprO] {
  override def left(l:A):B = ReprB(data - l.data)
  override def join(l:A, r:B):C = ReprC(l.data + r.data)
}

したがって、抽象型と型パラメーターの両方が抽象化の作成に使用されます。どちらにも弱点と長所があります。抽象型は、より具体的で任意の型構造を記述できますが、冗長であり、明示的に指定する必要があります。型パラメーターを使用すると、すぐに多くの型を作成できますが、継承と型の境界についてさらに心配する必要があります。

それらは互いに相乗効果をもたらし、それらの1つだけでは表現できない複雑な抽象化を作成するために組み合わせて使用​​できます。

19
ayvango