
Haskell Typeclasses vs. C++ Classes - MichaelBurge
http://www.michaelburge.us/2017/10/15/haskell-typeclasses-vs-cpp-classes.html
======
mbrock
Very detailed and informative!

For onlookers, I'd note that Haskell typeclasses are something of a trap for
beginners who think they are the way to accomplish object-like coding in
Haskell.

Actually, records with functions (closures) inside is usually a better choice:
simpler, more flexible, more straightforward.

Of course the resemblance between a vtable and a record of functions is very
obvious!

~~~
jonahx
> they are the way to accomplish object-like coding in Haskell. > Actually,
> records with functions (closures) inside is usually a better choice:

The better choice would be to use Haskell as it was intended to be used.

~~~
mbrock
And that doesn't include first class functions inside of data structures? :)

~~~
jonahx
I was just responding to "object-like coding," which i took to mean "using the
OO paradigm in haskell." sorry if that's not what you meant.

------
gpderetta
The closest thing to Typeclasses in C++ are concepts, i.e. the set of semantic
and syntactic requirements on template parameters.

As of C++17, concepts are not yet an actual language construct but are
enforced by convention and normally only checked at template instantiation
time (as opposed to definition time), although there are techniques that can
be used to simulate more formal checking (archetypes, enableif, etc.).

~~~
bstamour
They've been voted into C++20! The wait may finally be over (in a few more
years) :-)

~~~
gpderetta
That's excellent, I missed the news.

Unfortunately concepts lite are not capable of type checking declarations, but
it is still an improvement.

------
wyc
It may even be fair to say that Haskell's typeclasses make it more like an
object-oriented programming language. They have things like properties and
multiple inheritance. Even Wadler has a section leading with "Object-oriented
programming." in his paper on parametric polymorphism:

[https://people.csail.mit.edu/dnj/teaching/6898/papers/wadler...](https://people.csail.mit.edu/dnj/teaching/6898/papers/wadler88.pdf)
(page 3)

~~~
bad_user
_Polymorphism_ — If you understand by OOP the single-dispatching made at
runtime, then type classes are different because type classes are solved at
compile time, the "vtable" (the dictionary of functions) being passed around
isn't virtual and is provided per type, not per instance.

 _Encapsulation_ — If you understand by OOP the encapsulation of data, then
no, type classes have no such thing.

 _Inheritance_ — type classes don't have inheritance. What they have are
constraints (e.g. you can implement typeclass X for type T if a typeclass
implementation Y also exists for T). So you can say for example that
_MonadError_ can be implemented only for types already implementing _Monad_.
That's not inheritance, but _composition_.

Most importantly perhaps and something that doesn't clearly follow from the
above is that OOP as implemented in popular languages, including C++, gives
you _subtyping_ , whereas type classes do not.

This has important ramifications. For example _downcasting_ isn't possible.
And you no longer have the "liskov substitution principle". And subtyping is
actually incompatible with Hindley-Milner type inference.

Also in actual usage the differences can be quite big — for example, if you
view type classes as "restrictions" on what a type can do (like OOP interfaces
are), you no longer need to add these restrictions on the types themselves,
but on the operations, where you actually need them, while the data structure
definition can remain free of such restrictions.

This is called " _constrained parametric polymorphism_ " btw. Which can't be
achieved in most OOP languages that I know of, except for Scala and that's
because Scala has "implicit parameters" which are equivalent to Haskell's type
classes.

At the end of the day, what a type class gives you is a way to transform a
type name into a meaningful value. Imagine having a function like ...

    
    
         f: T => A
    

But the T param is a type name. That's what type classes give you, whereas OOP
does not.

~~~
Tarean
> type classes are different because type classes are solved at compile time
    
    
        data Nested a = Nest a (Nested [a]) | Done deriving Show
    
        build :: Int -> a -> Nested a
        build 0 a = Done
        build i a = Nest a $ build (i- 1) [a]
    
        main = do
            i <- readLn
            print (build i i)
    
    
    
    

if you build a value of Nested at runtime then it is impossible to do static
dispatch and the dictionary has to be constructed at runtime as well.

Sorry that I mistyped the example at first. If you run this version:

    
    
        > main
        23
        Nest 23 (Nest [23] (Nest [[23]] (Nest [[[23]]] (Nest [[[[23]]]] (Nest [[[[[23]]]]] (Nest [[[[[[23]]]]]] (Nest [[[[[[[23]]]]]]] (Nest [[[[[[[[23]]]]]]]] (Nest [[[[[[[[[23]]]]]]]]] (Nest [[[[[[[[[[23]]]]]]]]]] (Nest [[[[[[[[[[[23]]]]]]]]]]] (Nest [[[[[[[[[[[[23]]]]]]]]]]]] (Nest [[[[[[[[[[[[[23]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[23]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[23]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]]]]]] (Nest [[[[[[[[[[[[[[[[[[[[[[23]]]]]]]]]]]]]]]]]]]]]] Done))))))))))))))))))))))
    
    

The innermost Nested has type `Nest
[[[[[[[[[[[[[[[[[[[[[[Int]]]]]]]]]]]]]]]]]]]]]]` which wasn't known at compile
time so the show instance has to be constructed at runtime. This is called
polymorphic recursion.

~~~
rocqua
Technically, one might argue that i doesn't store a vtable, but only a
reference to a type, which stores a vtable determined at runtime.

I'd say the difference is minor to non-existent.

~~~
bad_user
"i" does not store a reference to a type. Values in Haskell aren't tagged with
their type like in OOP.

There is no "isInstanceOf" check in Haskell.

------
zzalpha
I kinda wish the Haskell designers hadn't gone with the term "typeclass", as
it's incredibly misleading.

Typeclasses have a lot more cognitive similarity to Go-style interfaces or
Smalltalk protocols than they do to OOP-style classes, and that fact can be
enormously confusing to newcomers (which, at times, seems to be the Haskell
raison d'etre).

~~~
dualogy
Well this stuff happened in '88:
[http://homepages.inf.ed.ac.uk/wadler/papers/class-
letter/cla...](http://homepages.inf.ed.ac.uk/wadler/papers/class-letter/class-
letter.txt) \--- they possibly didn't expect OOP and "classes" (Stroustrup
'79) to become _quite so very ubiquitous_ as they did in the 90s.

~~~
zzalpha
You... don't really think Stroustrup invented the term "class", do you? :)

~~~
dualogy
Of course not but that sort of meaning for the term class was GP's point here

~~~
zzalpha
And my point is that that usage of the term predates Stroustrup by 10 solid
years.

By the time Haskell was first released, OOP was either already available in
popular languages (Smalltalk, Object Pascal) or being introduced into existing
languages (Objective C, C++, Lisp).

In hindsight it would appear there was no reason _not_ to see that OOP was
gaining in popularity, that the term "class" was already in use, and that
overloading it would be confusing.

That said, in the end, all my original post was lamenting is that it's a shame
that Haskell terminology went the way it did... it's just yet another aspect
of the language that creates confusion among newcomers, and it already has
enough of that already! :)

~~~
iainmerrick
I don't think the creators of Haskell were concerned even slightly with trying
to fit in with trends in imperative languages. They were doing their own thing
(or at least, trying to make a research-friendly version of David Turner's
commercial language Miranda).

~~~
zzalpha
Your explanation does not justify the choice. They were tasked with
communicating with other academics who were already using the term "class" in
the field of programming language research.

~~~
mitchty
To be blunt, it doesn't need to be justified. Should I call cars horseless
carriages simply because that was the original term? Have you seen how bad the
situation is in mathematics where the exact same concept has multiple names?
This isn't a unique thing to have happen. Nor one to fret this much over.

The haskell committee were concerned with conveying knowledge amongst
functional programming researchers. The use of class in similar but unrelated
fields doesn't mean everyone need use it.

~~~
harry8
If you call a fish a "car" don't be surprised when people get confused and
think that choice of name was ill-advised. Car is already in use, don't use
car to describe a fish, it's not difficult. When a word has an accepted
meaning, using that word to convey an entirely different meaning where you
could easily choose something else is ridiculous. It is right to criticise
anyone who does something so silly.

Or you can think I'm just flying the mountain with ice cream windows if you
like.

~~~
iainmerrick
It depends whether you care about catering to a general audience.

Lisp got away with an entirely different meaning for “car” for a pretty long
time!

------
timthelion
To me, type-classes are used like a less flexible form of duck typing. I would
really like to see a more flexible strongly typed duck-typing than type-
classes. I think that a sufficiently advanced IDE could auto-generate, from an
expression, a "duck-type" for each untyped object used. For example, if you
have the expression:

    
    
        (given) g :: Int -> String
        function a =
         let
          l = (f a)
         in
          g l
    

Would it be possible to generate the duct-type

"function :: (a which has a method f :: a -> Int) -> String"

Now you're not saying that a has to belong to a typeclass, you are saying that
a has a duck-type of any value with a method f :: a -> Int.

~~~
default-kramer
I think OCaml does what you are looking for. See the example on
[https://en.wikipedia.org/wiki/Structural_type_system](https://en.wikipedia.org/wiki/Structural_type_system),
which features OCaml.

~~~
timthelion
Thanks, looks interesting. One of the things I've always wondered about with
this approach is, how do you then show the "duck type"s in a succinct and
human readable way.

This is pretty succinct, but I'd have to say that it is not obvious what the
syntax means:

    
    
         val y : < get_x : int; set_x : int -> unit > = <obj>
    

Perhaps the example was poorly chosen, what is "unit"?

~~~
wtetzner
Unit is a type that has exactly one value, (). It's often used like `void` is
used in C-like languages.

So this:

    
    
        val y : < get_x : int; set_x : int -> unit >
    

means an object that has a get_x method that returns an int, and a set_x
method that takes an int and returns unit (void).

