OOP vs type classes

From HaskellWiki

(this is just a sketch now. feel free to edit/comment it. I will include information you provided into the final version of this tutorial)

I had generally not used type classes in my application programs, but when
I'd gone to implement general purpose libraries and tried to maintain
as much flexibility as possible, it was natural to start building large
and complex class hierarchies. I tried to use my C++ experience when
doing this but I was bitten many times by the restrictions of type classes. After this experience, I think that I now have a better feeling and mind model
for type classes and I want to share it with other Haskellers -
especially ones having OOP backgrounds.

Brian Hulley provided us with the program that emulates OOP in Haskell - as
you can see, it's much larger than equivalent C++ program. An equivalent translation from
Haskell to C++ should be even longer :)

1 Everything is object?

You all know this OOP motto - "everything is object". While I program in
C++, it was really hard to do it without classes. But when I - the same
John de Mac-Lee programmer - use Haskell, it's hard to find open
vacancies for type classes. Why it is so different?

C++ classes pack functions together with data and make it possible to
use different data representations via the same interface. As long as
you need any of these facilities, you are forced to use classes.
Although C++ supports alternative ways to implement such functionality
(function pointers, discriminated unions), these are not as handy
as classes. Classes are also the sole method to hide implementation
details. Moreover, classes represent a handy way to group related
functionality together, so I found myself sometimes developing classes that
contain only static functions just to group them into some
'package'. It's extremely useful to browse the structure of large C++ project
in terms of classes instead of individual functions.

Haskell provides other solutions for these problems.

1.1 Type with several representations: use algebraic data type (ADT)

For the types with different representations, algebraic data types
(ADT) - an analog of discriminated unions - are supported:

You can also imagine all the C++ machinery that is required to implement the
analog of our 5 line, ADT-based solution to say that objects are not
so great as you thought before :D Of course, C++ classes are usually
much larger but that's again Haskell benefit - it's so easy to define
types/functions that you may use much finer granularity.

As you see, ADTs together with type inference make Haskell programs
about 2 times smaller than their C++ equivalent.

1.2 Packing data & functions together: use (records of) closures

Another very typical class usage is to pack data together with one
or more functions to proceed them and pass this bunch to some
function. Then this function can call these functions to implement
some functionality and don't bother how it is internally implemented.
Hopefully Haskell provides better way to implement this - you can
directly pass any functions as parameters to other functions.
Moreover, you can construct passed functions on-the-fly and capture in
these definitions values of identifiers available at the call place,
creating so-called closures. In this way, you construct something like
object on-the-fly and don't even need a type class:

do x <- newIORef 0
proc (modifyIORef x (+1), readIORef x)

Here, we passed to proc two routines - one that increments value of
counter and another that reads its current value. Another call to proc
that uses counter with locking, might look as:

1.3 Hiding implementation details: use module export list

One more usage of OOP classes is to hide implementation details, making
internal data/functions inaccessible to class clients. Unfortunately, this
functionality is not part of type class facilities. Instead, you
should use the sole Haskell method of implementation hiding - module
export list:

Since the constructor for the data type Stack is hidden (the export
list would say Stack(Stk) if it were exposed), outside of this module
a stack can only be built from operations empty, push and pop, and
examined with top and isEmpty.

Dividing whole program into classes and using their hierarchy to
represent entire program structure is a great instrument for OOP
languages. Unfortunately, it's again impossible in Haskell. Instead,
program structure typically rendered in module hierarchy and inside
module - in its export list. Although Haskell language don't provide
facilities to describe hierarchical structure inside of module, we had
another tool to do it - Haddock, a de-facto standard documentation tool.

Here, Haddock will build documentation for module looking at its
export list. The export list will be divided into sections (whose
headers given with "-- *") and subsections (given with "-- **"). As
the result, module documentation reflect its structure without using
classes for this purpose.

2 Type classes is a sort of templates, not classes

At this moment C++/C#/Java languages has classes and
templates/generics. What is a difference? With a class, type
information carried with object itself while with templates it's
outside of object and is part of the whole operation.

For example, if == operation is defined in a class, the actual
procedure called for a==b may depend on run-time type of 'a' but if it
is defined in template, actual procedure depends only on template
instantiated (and determined at compile time).

Haskell's objects don't carry run-time type information. Instead,
class constraint for polymorphic operation passed in form of
"dictionary" implementing all operations of the class (there are also
other implementation techniques, but this don't matter). For example,

Comparing to C++, this is like the templates, not classes! As with
templates, typing information is part of operation, not object! But
while C++ templates are really form of macro-processing (like
Template Haskell) and at last end generates non-polymorphic code,
Haskell's using of dictionaries allows run-time polymorphism
(explanation of run-time polymorphism?).

Moreover, Haskell type classes supports inheritance. Run-time
polymorphism together with inheritance are often seen as OOP
distinctive points, so during long time I considered type classes as a
form of OOP implementation. But that's wrong! Haskell type classes
build on different basis, so they are like C++ templates with added
inheritance and run-time polymorphism! And this means that usage of
type classes is different from using classes, with its own strong and
weak points.

3 Type classes vs classes

Here is a brief listing of differences between OOP classes and Haskell type classes

3.1 Type classes is like interfaces/abstract classes, not classes itself

There is no data fields inheritance and data fields itself
(so type classes more like to interfaces than to classes itself)....

For those more familiar with Java/C# rather than C++, type classes resemble interfaces more than the classes. In fact, the generics in those languages capture the notion of parametric polymorphism (but Haskell is a language that takes parametric polymorphism quite seriously, so you can expect a fair amount of type gymnastics when dealing with Haskell), so more precisely, type classes are like generic interfaces.

Why interface, and not class? Mostly because type classes do not implement the methods themselves, they just guarantee that the actual types that instantiate the type class will implement specific methods. So the types are like classes in Java/C#.

One added twist: type classes can decide to provide default implementation of some methods (using other methods). You would say, then they are sort of like abstract classes. Right. But at the same time, you cannot extend (inherit) multiple abstract classes, can you?

So a type class is sort of like a contract: "any type that instantiates this type class will have the following functions defined on them..." but with the added advantage that you have type parameters built-in, so:

classEq a where(==):: a -> a ->Bool(/=):: a -> a ->Bool-- let's just implement one function in terms of the other
x /= y =not(x == y)

But downcasting is absolutely impossible - there is no way to get
subclass dictionary from a superclass one

3.4 Inheritance between instances

Inheritance between instances (in "instance" declaration) means
that operations of some class can be executed via operations of other
class, i.e. such declaration describe a way to compute dictionary of
inherited class via functions from dictionary of base class:

classEq a where(==):: a -> a ->Boolclass Cmp a where
cmp :: a -> a ->Orderinginstance(Cmp a)=>Eq a where
a==b = cmp a b == EQ

This results in that any function that receives dictionary for Cmp class
can call functions that require dictionary of Eq class

3.5 Downcasting is a mission impossible

Selection between instances are done at compile-time, based only on
information present at this moment. So don't expect that more concrete
instance will be selected just because you passed this concrete
datatype to the function which accepts some general class:

Here, the first call will return "int", but second - only "Num".
this can be easily justified by using dictionary-based translation
as described above. After you've passed data to polymorphic procedure
it's type is completely lost, there is only dictionary information, so
instance for Int can't be applied. The only way to construct Foo
dictionary is by calculating it from Num dictionary using the first
instance.

3.6 There is only one dictionary per function call

For "eqList :: (Eq a) => [a] -> [a] -> Bool" types of all elements
in list must be the same, and types of both arguments must be the same
too - there is only one dictionary and it know how to handle variables
of only one concrete type!

3.7 Existential variables is more like OOP objects

Existential variables pack dictionary together with variable (looks
very like the object concept!) so it's possible to create polymorphic
containers (i.e. holding variables of different types). But
downcasting is still impossible. Also, existentials still don't allow
to mix variables of different types in a call to some polymorhic operation
(their personal dictionaries still built for variables of one concrete type):

There is a major difference though, in C++ (or java, or sather, or c#,
etc..) the dictionary is always attached to the value, the actual class
data type you pass around. in haskell, the dictionary is passed
separately and the appropriae one is infered by the type system. C++
doesn't infer, it just assumes everything will be carying around its
dictionary with it.

this makes haskell classes signifigantly more powerful in many ways.

classNum a where(+):: a -> a -> a

is imposible to express in OO classes, since both arguments to +
necessarily carry their dictionaries with them, there is no way to
statically guarentee they have the same one. Haskell will pass a single
dictionary that is shared by both types so it can handle this just fine.

in haskell you can do

class Monoid a where

mempty :: a

in OOP, this cannot be done because where does the dicionary come from?
since dictionaries are always attached to a concrete class, every method
must take at least one argument of the class type (in fact, exactly one,
as I'll show below). In haskell again, this is not a problem since the
dictionary is passed in by the consumer of 'mempty', mempty need not
conjure one out of thin air.

In fact, OO classes can only express single parameter type classes where
the type argument appears exactly once in strictly covariant position.
in particular, it is pretty much always the first argument and often
(but not always) named 'self' or 'this'.

class HasSize a where
getSize :: a ->Int

can be expressed in OO, 'a' appears only once, as its first argument.

Now, another thing OO classes can do is they give you the ability to
create existential collections (?) of objects. as in, you can have a
list of things that have a size. In haskell, the ability to do this is
independent of the class (which is why haskell classes can be more
powerful) and is appropriately named existential types.

data Sized = exists a . HasSize a => Sized a

what does this give you? you can now create a list of things that have a
size [Sized] yay!

and you can declare an instance for sized, so you can use all your
methods on it.

instance HasSize Sized where
getSize (Sized a)= getSize a

an exisential, like Sized, is a value that is passed around with its
dictionary in tow, as in, it is an OO class! I think this is where
people get confused when comparing OO classes to haskell classes. _there
is no way to do so without bringing existentials into play_. OO classes
are inherently existential in nature.

so, an OO abstract class declaration declares the equivalent of 3 things
in haskell: a class to establish the mehods, an existential type to
carry the values about, and an instance of the class for the exisential
type.

an OO concrete class declares all of the above plus a data declaration
for some concrete representation.

OO classes can be perfectly (even down to the runtime representation!)
emulated in Haskell, but not vice versa. since OO languages tie class
declarations to existentials, they are limited to only the intersection
of their capabilities, because haskell has separate concepts for them,
each is independently much much more powerful.

data CanApply = exists a b . CanApply (a -> b) a (b -> a)

is an example of something that cannot be expressed in OO, existentials
are limited to having exactly a single value since they are tied to a
single dictionary

classNum a where(+):: a -> a -> a
zero :: a
negate:: a -> a

cannot be expressed in OO, because there is no way to pass in the same
dicionary for two elements, or for a returning value to conjure up a
dictionary out of thin air. (if you are not convinced, try writing a
'Number' existential and making it an instance of Num and it will be
clear why it is not possible)

negate is an interesting one, there is no technical reason it cannot be
implemented in OO languages, but none seem to actually support it.

which would have the obvious interpretation, obviously it would only work
under the same limitations as OO classes have, but it would be a simple
way for haskell programs to declare OO style classes if they so choose.

(actually, it is still signifigantly more powerful than OO classes since
you can derive many instances, and even declare your own for classes
that don't meet the OO consraints, also, your single class argument need
not appear as the first one. it can appear in any strictly covarient
position, and it can occur as often as you want in contravariant ones!)

I suspect that most of the confusion come from the fact that people
believe just because virtual functions are attached to objects,
they cannot attach them to operations outside classes. That, to my
surprise, hints at a deeper misappreciation of both type classes and
so-called "OO" technology. Type classes are more OO than one might
realize.

The dictionary can be attached to the operations (not just to the values) by
using objects local to functions (which sort of matierialize the
dictionary). Consider

The key here is in the definition of operator+ which is just a formal
name for the real operation done by instance.add().

I appreciate that inferring and building the dictionary (represented
here by the "instance" local to operator+<T>) is done automatically by
the Haskell type system.
That is one of the reasons why the type class notation is a nice sugar.
However, that should not distract from its deerper OO semantics.

[...]

| in haskell you can do
|
| class Monoid a where
| mempty :: a
|
| in OOP, this cannot be done because where does the dicionary come from?

See above. I believe a key in my suggestion was "parameterized
abstract classes", not just "abstract classes".

5 Haskell emulation of OOP inheritance with record extension

Brian Hulley provided us the code that shows how OOP inheritance can be
emulated in Haskell. His translation method supports data fields
inheritance, although don't supports downcasting.

> although i mentioned not only pluses but also drawbacks of type
> classes: lack of record extension mechanisms (such at that implemented
> in O'Haskell) and therefore inability to reuse operation
> implementation in an derived data type...

You can reuse ops in a derived data type but it involves a tremendous amount
of boilerplate. Essentially, you just use the type classes to simulate
extendable records by having a method in each class that accesses the
fixed-length record corresponding to that particular C++ class.

Here is an example (apologies for the length!) which shows a super class
function being overridden in a derived class and a derived class method
(B::Extra) making use of something implemented in the super class:

I thanks Ralf Lammel and Klaus Ostermann for their paper
"Software Extension and Integration with Type Classes" (http://homepages.cwi.nl/~ralf/gpce06/) which prompts me to start thinking about differences between OOP and type classes instead of their similarities