Scala Context Bound or Type Classes
Context bounds are of the form
def func[A:B](p:A) = {
}
Which means there is in implicit scope something that can return a type of B[A].
eg I have classes Meal, Toy and a Rubbish. I write a lib to process all sorts of stuff into Rubbish.
package learningcats
case class Meal(name:String,
size:Int)
case class Toy(name:String,
plastic:Boolean)
// Create a trait for the operations you want in your lib
// and all operations take a param of the type class.
trait Rubbish[A] {
def getRecycleCost(what:A):Int
}
// Now the implicit converters
object ConvRubbish {
implicit object MealRubbish extends Rubbish[Meal] {
def getRecycleCost(what:Meal):Int = what.size
}
implicit object ToyRubbish extends Rubbish[Toy] {
def getRecycleCost(what:Toy):Int = if (what.plastic) 10 else 1
}
}
object MainRubbish extends App {
import ConvRubbish._
// Magic context bound sugar, A:Rubbish
def recycle[A : Rubbish](item:A) = implicitly[Rubbish[A]].getRecycleCost(item)
println(recycle(Toy("Action Man", true)))
println(recycle(Meal("Pizza", 1)))
}
So, you create some implicit instances which take Toy and Meal classes and return Rubbish[Meal] and Rubbish[Toy] types, even though Meal and Toy share no types they can now be processed as if they do.
The rest of it is syntax and practice.
Why Context Bound Classes
Ad-hoc polymorphism. You have a bunch of classes and you want to create a bunch of new operations which cut over some of them. Scala has special syntax which makes type classes first class citizens of the language. ie use this syntax to decouple classes, its core to the way we all should code in Scala.
My example is not using Algebraic Data Types or Json. Instead, lets say you have some objects in a kitchen and you modelled them all for cooking. Then you decide to make a monster game, and they are fighting in the kitchen and can grab stuff and attack each other with them. So, I’m going to add an attack operation to some of my kitchen implements by using a new AdHocWeapon data type.
A Context Bound Class simply converts one type to another. But that is not enough to explain why we want them. Instead, imagine a library developer wants to create a new function - say “ConvertFormat”. They want to then let you provide your own code to invoke ConvertFormat for all the types already in scala, and maybe your own Case Classes too. They can implement Context Bound case classes for all the base types, and tell you how to do the same.
Fighting with kitchen stuff
Here is the simple example. We are creating a new Type Class called an AdHocWeapon, and it has an attack operation.
Recap on the key points
Ad-hoc polymorphism - the Scala way to decouple classes. If you have classes in one domain which have nothing to do with a new set of operations in a different domain, use Type Classes.
The type class simply defines the new operations in the new domain you want - a typed base trait, and some implicit objects or vals which Scala can infer from the types, providing they are in scope, and there is only one that matches.
The companion object includes the initial implementations you want. Other devs can alter these or add more later on. The companion object effectively converts the classes you have already to extend the type class and provide the implementation.
You define the objects and vals in the companion object to be implicit so they can be pulled in by the type.
When using the type class, you should use a ‘Context bound type’ which is of the form def function[A:NameOfTypeClass], so we are saying that A is going to extend NameOfTypeClass, and that it will be pulled in from an implicit object or val which can convert it.
implicitly[AdHocWeapon[W2]] is sadly the syntax to invoke the new operations (methods) in the type class implementation. eg implicitly[AdHocWeapon[W2]].attack
Vampires fighting with bread boards still beat hobbits fighting with frying pans.
Type classes convert classes?
The example above I describe as adding methods, but I am passing in the weapon to the attack function, but the damage is fixed by the weapon type. Not only that, but I said to start with that type classes let you add operations to existing classes which is not what most articles say. I read quite a few blogs and watched talks, and I think the example above is simpler. However…
Take 2 - a library designed for Type Classes
Lets rewrite it, this time we have two domains, a kitchen with utensils, and then fantasy fighting which has creatures and weapons.
Presumably, during my library design I have pure weapons, but accept some sort of ‘OtherWeapon’ will be used. So the Type Class OtherWeapon is born and a battle resolution function is written for Weapon and also for OtherWeapon. This small expenditure up front lets anyone write an additional implicit OtherWeapon converter.
The output from this is:
Before Creature(Orc,80) vs Creature(Elf,110) Before Creature(Vampire,10000) vs Creature(hobbit,10) After Creature(Orc,70) vs Creature(Elf,85) After Creature(Vampire,9991) vs Creature(hobbit,9)
Weapon and OtherWeapon?
I could do another example, where I have Weapons, Utensils and then a type class which could be OffensiveWeapon. I could then add implicit to convert both to OffensiveWeapon, and have my battle module use OffensiveWeapon. I won’t do it now, this post is already too long. However, I think this would be a better design, as Weapons could be for status, defence or ornament.