Scala Tutorial
Basics
Control Statements
OOP Concepts
Parameterized - Type
Exceptions
Scala Annotation
Methods
String
Scala Packages
Scala Trait
Collections
Scala Options
Miscellaneous Topics
In Scala, type bounds are constraints that allow you to define which type variables a type parameter must be a subtype or supertype of. These bounds enhance type safety.
When we talk about a "lower bound", we're referring to a constraint indicating that a type parameter must be a supertype of another type. The opposite of this is the "upper bound", which specifies that a type parameter must be a subtype of another type.
Let's explore the concept of lower bounds in more detail.
The lower bound is defined using the >:
syntax. A type parameter with a lower bound means the type you use for that parameter must be a supertype of the bound.
Here's a simple example using Scala's collections:
def prepend[T >: Null](elem: T, list: List[T]): List[T] = { elem :: list }
In the above prepend
function, T >: Null
means that T
has a lower bound of Null
. In Scala, Null
is a subtype of all reference types (but not of value types like Int
, Double
, etc.). This ensures that whatever type T
you use, it must be a supertype of Null
.
Imagine we want to implement a class for a container that can hold elements of some type, and we want to have a method that can merge another container into the current one. If we're merging a container with a subtype, we should still be able to add its elements to our container. Here's how we can use lower bounds:
class Container[+T](val element: T) { def add[S >: T](newElement: S): Container[S] = new Container[S](newElement) }
The add
method says that we can add an element of type S
or any of its subtypes to our container. After adding, the container's type might change to a supertype, depending on what was added.
Lower bounds are beneficial when we want to ensure type safety while dealing with types and their supertypes. They're particularly helpful when combined with variance annotations in Scala, as in the Container
example above. While you might not use them as frequently as upper bounds, they're a powerful tool to have in your Scala toolbox.
Using Lower Bounds in Scala Generics:
Lower bounds in Scala generics allow specifying a lower limit for the type parameter.
class Container[A](val element: A) def processContainer[B >: String](container: Container[B]): Unit = { // Process container with elements of type B or its supertypes }
Lower Type Bounds in Scala Examples:
Lower type bounds ensure that the type parameter is at least a certain type or its subtype.
class Box[A](val content: A) def unpackBox[B >: Fruit](box: Box[B]): Unit = { // Unpack the box with content of type B or its supertypes (e.g., Fruit) }
How to Declare Lower Bounds on Type Parameters in Scala:
Declare lower bounds using the >:
syntax.
class Box[A](val content: A) def processBox[B >: String](box: Box[B]): Unit = { // Process box with content of type B or its supertypes (e.g., String) }
Working with Lower Bounds in Scala Collections:
Collections can also use lower bounds to accept elements of a specified type or its subtypes.
val fruits: List[Fruit] = List(new Apple, new Orange)
Scala Lower Bound Variance:
Lower bounds are related to contravariant positions in Scala. They allow flexibility in accepting supertypes.
trait Consumer[-A] { def consume(item: A): Unit }