What is the motivation for type-classes in Scala?
Asked Answered
G

1

9

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?

Grimona answered 10/4, 2013 at 13:3 Comment(0)
L
9

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.

Laywoman answered 10/4, 2013 at 13:54 Comment(3)
This is the exact reasoning. In general Type Classes can be more concise then they are in Scala. They way they are encoded allows them to be used with far more flexibility, then one can in something like vanilla Haskell, yet Haskell's implementation is much more concise. Another good example is adding something like Serialization to your Hierarchy, adding the interface to the top of the hierarchy would break all your old classes. Using type classes allows the functionality to be implemented incrementally, and specifically on only the needed types.Marder
Just as @Marder said, it's useful to stress out the fact that typeclasses are implemented in scala as a pattern (that is, not a language feature) but the concept is taken from other languages (like haskell) where the feature is embedded in the language itself. E.g haskell not being an OO language, there's no inheritance mechanism, so the typeclass feature is used to derive common functionality by custom function implementation on different data types. That's why typeclasses may seems redundant on scala, where there's a functionality overlap with object inheritance.Laywoman
Very good. I left this alone for a while and went back to the question on my own, trying to motivate it. I came up with a very similar argument. Nice!Grimona

© 2022 - 2024 — McMap. All rights reserved.