Context bounds are a syntactic sugar in Scala’s type system that help to express type classes more concisely. Type classes allow you to define a type-safe way of providing functionality that works across multiple, possibly unrelated, types. They allow you to define the behavior for selected types without modifying the actual class hierarchy or the type that the behavior applies to.
A simple example of a type class might be a math library that works with integer, floating-point, and complex numbers. We know all these types have basic arithmetic operations, such as addition and subtraction. A type class would let us define these operations without having to modify the built-in ‘Int‘, ‘Float‘, or ‘Double‘, and without having to extend some common trait or interface.
Here’s an example of the ‘Addable‘ type class that expects a type ‘A‘ and provides the addition operation for that type:
trait Addable[A] {
// Given two values of type A, provides a result of type A
def add(a: A, b: A): A
}
Now, to use this type class for basic arithmetic types like ‘Int‘, ‘Float‘, and ‘Double‘, we need to define instances of ‘Addable‘ for these types:
implicit object IntAddable extends Addable[Int] {
def add(a: Int, b: Int): Int = a + b
}
implicit object FloatAddable extends Addable[Float] {
def add(a: Float, b: Float): Float = a + b
}
implicit object DoubleAddable extends Addable[Double] {
def add(a: Double, b: Double): Double = a + b
}
Now, we would like to define a generic ‘sum‘ function that takes two parameters of type ‘A‘ and returns the sum using the ‘Addable‘ type class. We can do this with a type constraint using the ‘=>‘ syntax:
def sum[A](a: A, b: A)(implicit ev: Addable[A]): A = ev.add(a, b)
Here, ‘ev‘ is an implicit parameter whose type is ‘Addable[A]‘, which allows us to use type class operations.
Now comes the context bound. The method ‘sum‘ can be further simplified by using context bounds, which are syntactic sugar for the implicit type class requirement mentioned above. The context bound is denoted using the ‘<:‘ syntax in the type parameter declaration. Here’s how it looks:
def sum[A: Addable](a: A, b: A): A = implicitly[Addable[A]].add(a, b)
In this snippet, ‘A: Addable‘ is the context bound, which means that there must be an implicit instance of the ‘Addable[A]‘ type class in scope for the given type ‘A‘. This syntax is equivalent to the previous version using an implicit parameter.
With context bounds and the ‘sum‘ method defined above, you can provide any type that has an instance of the ‘Addable‘ type class and use it without worrying about the implementation details:
val intSum = sum(1, 2) // 3
val floatSum = sum(1.0f, 2.0f) // 3.0f
val doubleSum = sum(1.0, 2.0) // 3.0
In summary, context bounds are a way to simplify the syntax of expressing type classes in Scala. They enforce constraints on the types being used, ensuring that the behavior provided by the type class is available for those types. The context bounds syntax makes it easier to work with type classes and to write generic code that operates on multiple types.