Question

I am having some trouble motivating the use of type classes in Scala when comparing to upper bounds on types.

Consider the following code:

  case class NumList[T <: Complex](xs: Complex*) {
    def sum = (xs fold new Complex(0, 0))(_ + _)
    def map[U <: Complex](f: Complex => U): NumList[U] = NumList(xs.map(f): _*)
    override def toString = "[" + xs.mkString(", ") + "]"
  }

  case class GenList[T](xs: T*) {
    def sum(implicit num: Numeric[T]) = xs.sum
    def map[U](f: T => U) = GenList(xs.map(f): _*)
    override def toString = "[" + xs.mkString(", ") + "]"
  }

  val r = new Real(2)
  val n = new Natural(10)
  val comps = NumList(r, n, r, n)

  println(comps)
  println("sum: " + comps.sum)
  println("sum * 2: " + comps.map(x => x + x).sum)

  val comps2 = GenList(4, 3.0, 10l, 3d)
  println(comps2)
  println("sum: " + comps2.sum)
  println("sum * 2: " + comps2.map(_ * 2).sum)

While these two lists solve similar problems, one uses the numeric type-class and the other uses an upper bound on the type parameter. I understand the technical differences quite well, but I'm having a hard time getting to the core motivation of type-classes. The best motivation I found so far is the following:

While subclassing or implementing interfaces allows you to do mostly the same designs, type-classes allow you to specify features of a type on a per-method basis, whereas a generic class with type T and upper bound U constrains T everywhere where it is used. With this in mind, type-classes provide more fine-grained control over features of T in generic classes.

Are there any very clear examples motivating the pattern?

Was it helpful?

Solution

Trying to simplify one major aspect, typeclasses try to collect the behavior independently of your class hierarchy.

Suppose you need to define a new numeric type MetaNum (with standard numeric operations) but you can't or won't make it a subclass of your Complex type, for whatever reason.

With Numeric typeclass, you just need to provide an appropriate instance for your MetaNum, providing the needed operations.

Then you can create a GenList[MetaNum] and sum over it.

You can't do this with the NumList, because MetaNum is not a Complex. The implementation choice you made when defining NumList will stab back at you when you try to generalize your operation/data-structure in a second moment.

Conclusion
Typeclasses gives you more freedom to extend your behavior independently from hierarchical considerations, at the cost of some additional complexity and boilerplate.

I can't tell if you meant the same in your question.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top