Scala Variances, Upper type Bounds and Lower type bounds

Filed Under: Scala

Variance refers as how subtyping between complex types relates to subtypes of their components. Scala supports variances annotations of type parameters of a generic class.

The type of variance annotations supported in Scala are;

Types Definition Scala Notation
Covariant C[T’] is a subclass of C[T] [+T]
Contravariant C[T] is a subclass of C[T’] [-T]
Invariant C[T] and C[T’] are not related [T]

class Array[+X] {
  def add[Y >: X](elem: Y): Array[Y] = new Array[Y] {
    override def first: Y = elem
    override def retrieve: Array[Y] = Array.this
    override def toString() = elem.toString() + "\n" + Array.this.toString()
  }
  def first: X = sys.error("No elements in the Array")
  def retrieve: Array[X] = sys.error("Array is empty")
  override def toString() = ""
}
object ArrayTest extends App {

  var a: Array[Any] = new Array().add("US");
  //a = a.add(new Object())
  a = a.add(56)
  a = a.add(67.89)
  println("Array elements added are: " + a)

}

The annotation +X indicates that X can be used in covariant positions only. The annotation -X indicates that X can be used in contravariant positions only. Array[X] is a subtype of S if X is a subtype of S.

In this example we are defining a covariant type parameter X to define the method add. We have a polymorphic method in which we use the element type X as a lower bound of add method type variable thereby bringing the variance of X in sync with its declaration as covariant parameter. We are inserting elements of String,Integer and float types.

Upper type Bounds

An upper type bound X <: B declares that type variable X refers to a subtype of type B.

Consider an example below.


trait Employee {
  def EmployeeIdexists(x: Any): Boolean
}

case class EId(a: Int) extends Employee {
  def EmployeeIdexists(x: Any): Boolean =
    x.isInstanceOf[EId] &&
      x.asInstanceOf[EId].a == a
}

object TestEmployee extends App {
  def findEId[A <: Employee](d: A, ls: List[A]): Boolean =
    if (ls.isEmpty) false
    else if (d.EmployeeIdexists(ls.head)) true
    else findEId[A](d, ls.tail)
  val elist: List[EId] = List(EId(25), EId(26), EId(27), EId(28))
  if ((findEId[EId](EId(28), elist)))
    println("Employee Id exists")
  else println("Employee Id does not exist")

}

This example find whether the employee id exists or not. We are defining a method findEId which creates an integer list and checks whether the id specified is contained in the list.

Scala-trait-output

Lower type Bounds

The term X >: B expresses that the type parameter X or the abstract type X refer to a supertype of type B. Lower type bounds declare a type to be a supertype of another type.

Create the scala class ListNode as;


case class ListNode[+T](h: T, t: ListNode[T]) {
  def head: T = h
  def tail: ListNode[T] = t
  def prepend[U >: T](elem: U): ListNode[U] =
    ListNode(elem, this)
}

We are creating a class ListNode and defining the prepend method where T only appears in covariant positions. U >: T specifies the abstract type U is a supertype of T.

The type variable T appears as a parameter type of method prepend, this rule is broken. With the help of a lower type bound, though, we can implement a prepend method.

Now create a scala object as;


object LowerBoundTest extends App {

  val empty: ListNode[Null] = ListNode(null, null)
  val strList: ListNode[String] = empty.prepend("hello")
    .prepend("world")
  val anylist: ListNode[Any] = strList.prepend(12345)
  println(strList)
  println(anylist)
}

Scala-Lower-Type-Bounds

That’s all for now, we will look into more scala features in future posts.

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages