Type bounds in Scala

Type bounds are a feature in Scala that allow you to restrict the types that can be used as type parameters for a generic class or method. Type bounds provide a way to ensure that certain operations can be performed on the type parameter, and to prevent errors that might occur if the wrong type is used.

In Scala, there are two types of type bounds: upper bounds and lower bounds. An upper bound restricts the types that can be used as type parameters to a class or method to those that are a subclass of a specified type. A lower bound restricts the types that can be used as type parameters to a class or method to those that are a supertype of a specified type.

To define an upper bound in Scala, you use the `<:` operator followed by the upper bound type. For example, consider the following code that defines a generic `Container` class with an upper bound on the type parameter:

scala
class Container[T <: Comparable[T]](val value: T) {
  def compare(other: Container[T]): Int = value.compareTo(other.value)
}

In this example, a new `Container` class is defined with a type parameter `T` that must be a subclass of `Comparable[T]`. The `value` field is defined as a value of type `T`. The `compare` method is defined to take another `Container` object of the same type parameter `T`, and returns the result of calling the `compareTo` method on the `value` field of the current object with the `value` field of the other object.

To define a lower bound in Scala, you use the `>:` operator followed by the lower bound type. For example, consider the following code that defines a generic `Printer` class with a lower bound on the type parameter:

scala
class Printer[T >: Null] {
  def print(value: T): Unit = {
    if (value != null) println(value.toString)
  }
}

In this example, a new `Printer` class is defined with a type parameter `T` that must be a supertype of `Null`. The `print` method is defined to take a value of type `T`, and prints the string representation of the value to the console if the value is not `null`.

Type bounds can also be combined to define more complex constraints on the type parameter. For example, consider the following code that defines a generic `Pair` class with both an upper bound and a lower bound on the type parameter:

scala
class Pair[T >: Null <: Comparable[T]](val first: T, val second: T) {
  def max: T = {
    if (first.compareTo(second) > 0) first else second
  }
}

In this example, a new `Pair` class is defined with a type parameter `T` that must be a supertype of `Null` and a subclass of `Comparable[T]`. The`first` and `second` fields are defined as values of type `T`. The `max` method is defined to return the maximum of the `first` and `second` fields, based on the `compareTo` method of the type `T`.

Overall, type bounds are a powerful feature in Scala that allow you to restrict the types that can be used as type parameters for a generic class or method. Type bounds provide a way to ensure that certain operations can be performed on the type parameter, and to prevent errors that might occur if the wrong type is used. It is important to use type bounds judiciously and to follow the best practices for defining and using type bounds, such as providing clear documentation for the constraints on the type parameter, and using the least restrictive type bounds that are necessary to ensure correct behavior.