上限型境界 は型を別の型のサブタイプに制限しますが、下限型境界は型が別の型のスーパータイプであることを宣言します。表現B >: A
はパラメータB
または抽象型B
が型A
のスーパータイプであることを表します。ほとんどのケースでA
はそのクラスの型パラメータであり、B
はメソッドの型パラメータになります。
以下はこれが役立つ場合の例です。
trait Node[+B] {
def prepend(elem: B): Node[B]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend(elem: B): ListNode[B] = ListNode(elem, this)
}
このプログラムは片方向リストを実装します。Nil
は空の要素(すなわち空のリスト)を意味します。
class ListNode
は型B
(head
)の要素と、リストの残りの部分(tail
)への参照を持つノードです。
class Node
とそのサブタイプは、+B
とあるので、共変です。
しかしながら、このプログラムはコンパイル されません。prepend
のパラメータelem
が、宣言時に共 変と宣言した型B
になっているからです。
なぜ通らないかというと、関数はそれらの型パラメータに対して反変であり、それらの結果型に対して共変だからです。
これを解決するためには、prepend
のパラメータelem
の型の変位指定を逆転させる必要があります。
これを実現するには、下限型境界としてB
を持つ新しい型パラメータU
を導入します。
trait Node[+B] {
def prepend[U >: B](elem: U): Node[U]
}
case class ListNode[+B](h: B, t: Node[B]) extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
def head: B = h
def tail: Node[B] = t
}
case class Nil[+B]() extends Node[B] {
def prepend[U >: B](elem: U): ListNode[U] = ListNode(elem, this)
}
すると、以下のようなことができます。
trait Bird
case class AfricanSwallow() extends Bird
case class EuropeanSwallow() extends Bird
val africanSwallowList= ListNode[AfricanSwallow](AfricanSwallow(), Nil())
val birdList: Node[Bird] = africanSwallowList
birdList.prepend(EuropeanSwallow())
Node[Bird]
はafricanSwallowList
をアサインできますが、その後EuropeanSwallow
を受け入れられます。