There is a yet another special case with implicit parameters where they are parameterized with the types of normal parameters. In this case, our previous example could be rewritten as follows:
trait CanEqual[T] { def hash(t: T): Int }
def equal[CA, CB](a: CA, b: CB)(implicit ca: CanEqual[CA], cb: CanEqual[CB]): Boolean =
ca.hash(a) == cb.hash(b)
As we have already mentioned, there is also some syntactic sugar for this case named context bounds. With context bounds, our example can be simplified as follows:
def equalBounds[CA: CanEqual, CB: CanEqual](a: CA, b: CB): Boolean = {
val hashA = implicitly[CanEqual[CA]].hash(a)
val hashB = implicitly[CanEqual[CB]].hash(b)
hashA == hashB
}
As in the previous case, this syntax becomes concise in the case of the implicit parameters being passed over to the internal function:
def equalDelegate[CA: CanEqual, CB: CanEqual](a: CA, b: CB): Boolean = equal(a, b)
Now, this is short and readable!
What is missing is the implementation of the implicit parameters for the different CA and CB. For String, it might be implemented as follows:
implicit val stringEqual: CanEqual[String] = new CanEqual[String] {
def hash(in: String): Int = in.hashCode()
}
The implementation for Int is done in a very similar way. Using single abstract method syntax, we can replace the class definition with a function:
implicit val intEqual: CanEqual[Int] = (in: Int) => in
We can do this with even shorter code by using the identity in curried form:
implicit val intEqual: CanEqual[Int] = identity _
Now, we can use our implicit values to call functions with context bounds:
scala> equal(10, 20)
res5: Boolean = false
scala> equalBounds("10", "20")
res6: Boolean = false
scala> equalDelegate(10, "20")
res7: Boolean = false
scala> equalDelegate(1598, "20")
res8: Boolean = true
In the previous snippet, the compiler resolves different implicits for different types of parameters, and these implicits are used to compare the arguments of the function.