Type bounds are a way to define restrictions on the types that can be used as generic parameters in Scala, allowing you to create code that enforces certain relationships or constraints between types. There are two kinds of type bounds in Scala: upper type bounds and lower type bounds.
1. Upper Type Bounds:
Upper type bounds define an upper limit on a type parameter. They are specified using the ‘<:‘ symbol. If you define an upper type bound for a type parameter ‘T‘, it means that T can be any type that is a subtype of the given bound.
For example, let’s consider a simple trait hierarchy and a generic class that takes a type parameter with an upper type bound:
trait Animal
trait Mammal extends Animal
trait Reptile extends Animal
class PetBox[T <: Animal](val pet: T)
val dogBox = new PetBox[Mammal](new Mammal { })
val lizardBox = new PetBox[Reptile](new Reptile { })
Here, ‘Animal‘ is a trait that ‘Mammal‘ and ‘Reptile‘ extend. The class ‘PetBox‘ takes a type parameter ‘T‘, where ‘T‘ is a subtype of ‘Animal‘. This means that you can create instances of ‘PetBox‘ with ‘Mammal‘ and ‘Reptile‘, but not with a type that is not a subtype of ‘Animal‘.
2. Lower Type Bounds:
Lower type bounds define a _lower_ limit on a type parameter by specifying a lower bound. They are specified using the ‘>:‘ symbol. If you define a lower type bound for a type parameter ‘U‘, it means that U can be any type that is a supertype of the given bound.
Here’s an example of a lower type bound in action:
trait Animal {
def speak: String
}
class Mammal() extends Animal {
def speak: String = "Mammal noise"
}
class Reptile() extends Animal {
def speak: String = "Reptile noise"
}
class AnimalHandler[T >: Mammal](val animal: T)
val handler1 = new AnimalHandler(new Mammal) // OK
val handler2 = new AnimalHandler(new Animal {}) // OK, because Animal is a supertype of Mammal
// This would not compile, because Reptile is not a supertype of Mammal:
// val handler3 = new AnimalHandler(new Reptile)
Here, ‘AnimalHandler‘ takes a type parameter ‘T‘ with a lower type bound of ‘Mammal‘ (‘T >: Mammal‘). This means you can create an instance of ‘AnimalHandler‘ with ‘Mammal‘ or its supertypes, but not with a type that is not a supertype of ‘Mammal‘.
In summary, upper type bounds use the ‘<:‘ symbol to enforce a type parameter to be a subtype (or equal) to a given type, while lower type bounds use ‘>:‘ to require a type parameter to be a supertype (or equal) to a given type. Using these concepts, one can create more expressive and flexible generic components in Scala.