In Scala you can impose a constraint on a type parameter (T) saying that, T has to have a binding with another type. I hope this sounds too abstract. Lets write an example,
Consider there are 2 types
case class Address(doorNo: Int, streetName: String, area: String, city: String, pincode: Int, country: String) case class Name(sal: String, fn: String, ln: String)
we are to build a formatted String out of these two types meaning, the common agenda is to build a formatted string . Lets define a type for that too so that we can pass any type.
trait LabelMaker[T] { def printLabel(t: T): String }
Now lets implement the trait to implement formatted string for Address and Name case classes.
object LabelMaker { implicit object IndianAddressLabelMaker extends LabelMaker[Address] { def printLabel(t: Address): String = s"""%d, %s, |%s, |%s - %d, |%s """.stripMargin.format(t.doorNo, t.streetName, t.area, t.city, t.pincode, t.country) } implicit object NameLabelWriter extends LabelMaker[Name] { override def printLabel(t: Name): String = """%s.%s %s""".format(t.sal, t.fn, t.ln) } }
(Note : type enthusiast would have figured out that Iam speaking about type classes !!!)
Now Lets use LabelMaker to format our types a naive implementation would like below
def printLabel[T](t:T)(lm:LabelMaker[T]) = lm.printLabel(t) val x: (LabelMaker[Address]) => String = printLabel(address)
it works, but x results in another function where we have to pass an implementation of LabelMaker.
Lets improve this such that we can infer the LabelMaker from surrounding context using implicits.
val address = Address(4, "pkwms", "sw", "c", 600015, "I") def printLabel[T](t:T)(implicit lm:LabelMaker[T]) = lm.printLabel(t) val x: String = printLabel(address)
now that we are not passing any LabelMaker implementation.
Here comes the deal breaker, now lets say that we have another type called DateOfBirth and we want to create a formattedString out of it.
case class DateOfBirth(day: String, dateOfMonth: Int, month: String, year: Int)
val dob = DateOfBirth("Fri", 15, "Jun", 1984) printLabel(dob)
this won't compile as we don yet have the implementation for DateOfBirth. (Note: none of the IDE's today could catch this error inline as you code, it get caught when you compile)
Lets change the printLabel function,
def printLabel[T: LabelMaker](t: T) = implicitly[LabelMaker[T]].printLabel(t)
As you can see we removed the implicit parameter and used something called implicitly , this is where context bounds could be used , If you see in the PrintLabel object we have implicit object which Implement LabelMaker such that we are saying that Address and Name types has a bound to an Implementation of LabelMaker. This is being represented in the function such that it expects that the generic type parameter T has a bound to LabelMaker as defined in [T:LabelMaker]. The compiler will throw an error if it could not find the binding to LabelMaker for T.
In the right side of the function we use implicitly keyword to access the printLabel funciton from the implicit parameter.
so when we use this nothing really changes ....
Consider having all the traits , case classes and companion traits in the same package to avoid confusion.
I hope this post had helped you to get started with context bounds..