In Scala, we can use at least two methods to retrofit existing or new types. Suppose we want to express that something can be quantified using an Int
. We can define the following trait.
Implicit conversion
trait Quantifiable{ def quantify: Int }
And then we can use implicit conversions to quantify e.g. Strings and Lists.
implicit def string2quant(s: String) = new Quantifiable{
def quantify = s.size
}
implicit def list2quantifiable[A](l: List[A]) = new Quantifiable{
val quantify = l.size
}
After importing these, we can call the method quantify
on strings and lists. Note that the quantifiable list stores its length, so it avoids the expensive traversal of the list on subsequent calls to quantify
.
Type classes
An alternative is to define a "witness" Quantified[A]
that states, that some type A
can be quantified.
trait Quantified[A] { def quantify(a: A): Int }
We then provide instances of this type class for String
and List
somewhere.
implicit val stringQuantifiable = new Quantified[String] {
def quantify(s: String) = s.size
}
And if we then write a method that needs to quantify its arguments, we write:
def sumQuantities[A](as: List[A])(implicit ev: Quantified[A]) =
as.map(ev.quantify).sum
Or using the context bound syntax:
def sumQuantities[A: Quantified](as: List[A]) =
as.map(implicitly[Quantified[A]].quantify).sum
But when to use which method?
Now comes the question. How can I decide between those two concepts?
What I have noticed so far.
type classes
- type classes allow the nice context bound syntax
- with type classes I don't create a new wrapper object on each use
- the context bound syntax does not work anymore if the type class has multiple type parameters; imagine I want to quantify things not only with integers but with values of some general type
T
. I would want to create a type classQuantified[A,T]
implicit conversion
- since I create a new object, I can cache values there or compute a better representation; but should I avoid this, since it might happen several times and an explicit conversion would probably be invoked only once?
What I expect from an answer
Present one (or more) use case(s) where the difference between both concepts matters and explain why I would prefer one over the other. Also explaining the essence of the two concepts and their relation to each other would be nice, even without example.
size
of a list in a value and say that it avoids the expensive traversal of the list on subsequent calls to quantify, but on your every call to thequantify
thelist2quantifiable
gets triggered all over again thus reinstantiating theQuantifiable
and recalculating thequantify
property. What I'm saying is that there is actually no way to cache the results with implicit conversions. – Nikita Volkov