Friday, August 20, 2010

Type Class Utopia Part One

I have been working through a personal dilemma recently, namely that of deciding what the perfect solution would be for making the ultimate type class implementation. I have seen a series of dialogues based around Scala type classes in particular over the last few weeks, and it is this which made me want to look back to Haskell, to see if Scala has 'caught up' with it.

I can only speak with the benefit of experience on type classes in two languages, Haskell and Scala, an old, elegant, 'firm favourite', and a more recently found 'get things done', powerhouse. My Haskell days are mostly behind me now as I must admit to never really being able to use it in enterprise. Nonetheless it is still to me the most elegant, pure, beautiful language. My Scala experience is limited to a little over three years, with only two years worth of putting it to work in the enterprise.

If we start with Haskell and a contrived example, a Difference Engine, somewhat simpler than Charles Babbage's version. By chance we have a need at work to do something along these very lines, basically a pluggable calculation engine.

data CalculableEntity = CalculableEntity { x :: Int, y :: Int }

class DifferenceEngine a where
calculateDifference :: a -> Int

instance DifferenceEngine CalculableEntity where
calculateDifference (CalculableEntity a b) = a - b

calculate a = calculateDifference(a)

So, that is pure, elegant, beautiful even. Moreover it is easy to take it further and calculate on the Double type or any other, we just need a new instance working with the new type. So far there is no form of obfuscation in my opinion, it can not as far as I can see be made any simpler to a developer trying to understand intent. Is this perfection ? Let us come back to that when we have explored it further a little later.

Let us look at the Scala equivalent.

case class CalculableEntity(x: Int, y: Int)

trait DifferenceEngine[T] {
def calculateDifference(ce: T): Int

def calculate[T](t: T)(implicit de: DifferenceEngine[T]) = de.calculateDifference(t)

object DifferenceEngine {
implicit object CEDifferenceEngine extends DifferenceEngine[CalculableEntity] {
def calculateDifference(ce: CalculableEntity) = {
ce.x - ce.y

This does basically the same thing. Personally I look at this and immediately see clutter. Now some of that we can excuse as Scala marries the functional and object oriented paradigms, and excellently too. One consequence of this is that Scala's type system can not be as elegant as Haskell's. What worries me more though is the use of implicits. Implicits offer us great power, but with great power comes great responsibility, and all of these extra keywords in my opinion add more clutter, further obfuscating the intent of the code.

Here it is almost like we are losing our identity a little with all of this wrapping and the sprinkling of magic keywords. I understand that we are delegating to the compiler, but in so doing we are polluting the reference manual by which our audience will understand our intent.

So ends round one, and in my opinion Haskell comes out on top. I think Charles Babbage would have backed Haskell thus far. In part two I will explore where Scala may have an edge over Haskell.


James Iry said...

For a marriage of something like Scala-implicits with a much more Haskellish language see

Ross McDonald said...

Thanks for the pointer James. That is my weekend reading sorted :-)

Debasish said...

Scala implicits add more power than Haskell's type classes. Have a look at Section 3.2 of this paper Generics of a Higher Kind ( Scala, unlike Haskell, supports OO and it's not easy to marry subtyping with functional paradigms.

Ross McDonald said...

Hi Debasish.

Again thanks for the pointer, I should be able to refine some material for part two of this post based on reading it.

I totally agree that what the Scala team have done in marrying the two paradigms is amazing.

I suppose that my objective in this first part was to compare for simple use cases, and at that level I believe Haskell's implementation is purer and easier to digest.