Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

I was having some discussion on type classes in Scala with Daniel
on Twitter the other day when I suddenly discovered one of my
unfinished posts on the same topic. Not that there's anything new that
you will get to know from this rant. But these are some of my thoughts
on the value that type class based thinking adds to your design space.
I started this post when I published my thoughts on orthogonality in design some time back.

Let's start with the GOF Adapter pattern .. the object adapter that
uses the highly recommended composition technique to bind abstractions.
The example is also the same which I used for discussing orthogonality
in design ..

// the adapter provides the interface of the LabelMaker on an AddressAddressLabelMaker().toLabel(Address(100, "Monroe Street", "Denver", "CO", "80231"))

Now what's the incidental complexity that we introduce in the above design ?

As a client our point of focus has shifted to the adapter class AddressLabelMaker, which wraps our original abstraction, the Address instance. This leads to an identity crisis of the Address
instance itself, a typical problem with any wrapper based idiom. The
identity of the adaptee is now lost within the identity of the adaptor.
And if you are brave enough to have the adaptee as an aggregate member
of another object, then obviously the entire composition of the adapter
idiom breaks down.

Conclusion : Object adapters don't compose. And the class
variant of the adapter pattren is worse in the sense that you have a
wiring by inheritance right from start, which makes the entire design
much more rigid and coupled.

Enter Type Class ..

In the above structure of the adapter pattern, the adaptee was wrapped
into the adapter leading to the identity loss of the adaptee that we
discussed earlier. Let's take a different approach and see if we can
abstract the adapter away from the client usage. The type class
approach does exactly offer this way of composing abstractions - the
type system of the language selects the appropriate adapter instance
during compile time, so that the user can program explicitly to the
adaptee.

Consider this function printLabel, that takes an argument and prints the label using the LabelMaker that we supply to it ..

def printLabel[T](t: T)(lm: LabelMaker[T]) = lm.toLabel(t)

In order to make a label out of an Address, we
need to define the adapter that does it. In Scala we have first class
module support through the object syntax. Let's define a module that
defines the semantics to convert an Address to a LabelMaker.

Note that we make the adapter a Scala object with an implicit
qualifier. What this does is that the compiler supplies the implicit
argument if it finds one appropriate instance within the lexical scope.
But for that we need to have the LabelMaker argument to printLabel implicit as well.

We don't supply the implicit argument at all, instead use the context
bound syntax where the compiler automatically picks up the appropriate
instance from the enclosing lexical scope. In the above example the implicit object AddressLabelMaker needs to be in scope of the function where you call printLabel.
It will complain if it doesn't find one - hence you are alerted during
compile time itself. Look ma - No evil run time failures ..

Now if we want to print a label for an Address, we do .

printLabel(Address(100, "Monroe Street", "Denver", "CO", "80231"))

No incidental complexity in the client code in the form of adapter
objects, the abstractions are explicit, we only supply the object for
which we want to print the labels. We get rid of indirections as well.
Not only that, if you look at the various abstractions that constitute
the surface area of the entire design, modularity speaks for itself.
The client only programs on the type class definition while the type
class instance is nicely abstracted away *only* for the eyes of the
compiler.

Let's see how Haskell does it ..

We have come this far discussing type classes without mentioning
Haskell once. Let's make amends and see how the above design fits in
the pure functional world of Haskell.

LabelMaker is the type class - let's define it in Haskell ..

class LabelMaker a where toLabel :: a -> String

This corresponds to our LabelMaker trait definition in Scala.

We want to make Address as a LabelMaker. Here's an instance of the above type class for Address ..

And now we can define printLabel that uses the type class and generates the String for the label ..

printLabel :: (LabelMaker a) => a -> StringprintLabel a = toLabel(a)

This corresponds to the Scala definition of printLabel that we have above.

A little observation ..

Note that one place where Haskell is much less verbose than Scala in
the above implemnetation is the instance definition of the type class.
But here the added verbosity in Scala is not without a purpose and
offers a definite advantage over the corresponding Haskell definition.
In Scala's case we name the instance explicitly as AddressLabelMaker,
while the instance is unnamed in case of Haskell. The Haskell compiler
looks into the dictionary on the global namespace for a qualifying
instance to be picked up. In Scala's case the search is performed
locally in the scope of the method call that triggered it. And because
it's an explicitly named instance in Scala, you can inject another
instance into the scope and it will be picked up for use for the
implicit. Consider the above example where we have another instance of
the type class for Address that generates the label in a special way ..

Type classes define a set of contracts that the adaptee type needs to
implement. Many people misinterpret type classes synonymously with
interfaces in Java or other programming languages. With interfaces the
focus is on subtype polymorphism, with type classes the focus changes
to parametric polymorphism. You implement the contracts that the type
class publishes across unrelated types.

A type class implementation has two aspects to it :-

defining the contracts that instances need to implement

allow selection of the appropriate instance based on the static type checking that the language offers

Haskell
uses the class syntax to implement (1), though this class is a
completely different concept than the one we associate with OO
programming. For Scala we used traits to implement this feature and an
extension of the trait into an object to implement instances of it.

As I mentioned earlier, Haskell uses a global dictionary to implement
(2), while Scala does it through a lookup in the enclosing scope of
invocation. This gives an additional flexibility in Scala where you can
select the instance by bringing it into the local scope.

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!