上限型境界 は型を別の型のサブタイプに制限しますが、下限型境界は型が別の型のスーパータイプであることを宣言します。表現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を受け入れられます。