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).
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).
The whole world of GUI programming was based on OO.
The Xerox Star was introduced in 1981 https://en.wikipedia.org/wiki/Xerox_Star
Apple's Lisa was introduced in 1983. https://en.wikipedia.org/wiki/Apple_Lisa
With it, Lisa Toolkit and Clascal (Pascal with Classes) http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/app...
The Mac in 1984 https://en.wikipedia.org/wiki/Macintosh_128K
MacApp in 1985 https://en.wikipedia.org/wiki/MacApp
The Tektronix 4404 running Smalltalk, also 1984 http://www.wirfs-brock.com/allen/files/tek/4404-Flyer.pdf
The Amiga and Atari ST in 1985.
Windows was at 2.0 in 1987.
Heck, the NeXT cube had been introduced by fall of '88.
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! :)
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.
Or you can think I'm just flying the mountain with ice cream windows if you like.
Lisp got away with an entirely different meaning for “car” for a pretty long time!
I'm saying in the broader context of programming languages, it's confusing. And that that confusion could have been easily foreseen and avoided... after all, Smalltalk had been using the term "class" for almost 20 years before Haskell appeared and introduced their terminology.
At least they're called "typeclasses" and not just "classes". But we're always going to have some overloading of these powerful short ancient words.
I did recently see a suggestion to rename the "typeclass" keyword to "algebra" which would be pretty cool!
I am not sure what other name you would suggest, anyway.
interface Eq a where
(==), (/=) :: a -> a -> Bool
x /= y = not (x == y)
implement (Eq a) => Eq (Tree a) where
Leaf a == Leaf b = a == b
(Branch l1 r1) == (Branch l2 r2) = (l1==l2) && (r1==r2)
_ == _ = False
Not bad. But not a total game changer either. Probably makes more immediate sense for most new comers though.They could have used a fashionable and somewhat overloaded term like "interface" or "protocol" or "trait" or "flavor", or they could have used an exact, albeit somewhat bland, name coming from mathematics. They did the latter.
In fact, they have faced a similar choice for many aspects of the language, and in most cases, mathematical purity got precedence over practicality. Had they chosen differently, the result wouldn't be Haskell.
Why not? They seem to be exactly interfaces to me.
> They could have used a fashionable and somewhat overloaded term like "interface" or "protocol" or "trait" or "flavor", or they could have used an exact, albeit somewhat bland, name coming from mathematics. They did the latter.
The terminology "class" in Haskell certainly has nothing to do with its mathematical meaning of "collection of sets defined by a predicate".
If we are talking about interface like in Java, for instance, then the important difference is that in Java, class specifies what interfaces it has, while in Haskell, any code can specify how a type is a member of a type class (actually there can be multiple different ways how the same type can be member of same type class). Another difference was, until recently, that the type classes can define "default" implementations of functions. And there are other differences related to "deriving" statement.
> The terminology "class" in Haskell certainly has nothing to do with its mathematical meaning of "collection of sets defined by a predicate"
I don't want to get into philosophy of math, I am not strong in it, but "class" in mathematics is certainly not limited to collections of sets. For example, there is a class of all classes, but not set of all sets.
The predicate that defines the type class in Haskell is defined by the collection of all the relevant "instance" statements.
Actually, now that I think about it, as I note above, there are multiple ways that one type can be made a member of one type class, so it's certainly not a membership in the set-theoretic sense.
Let's say you have some function called GetStringableThing() that returns returns, as its name suggests, some object that implements the Stringable interface -- where Stringable is just an interface that consists of a ToString() method. As the caller of GetStringableThing(), you have no say in precisely which type of Stringable thing you'll get -- that's up to the callee. So there exists some type that implements Stringable, which GetStringableThing() would be more than happy to give you. This is in contrast with universal quantification; if the return type of GetStringableThing() was universally quantified, that would mean that you as the caller get to dictate the return type (so long as it implements Stringable), and its the callee's responsibility to be able to return any type the caller demands.
Typeclasses (as in Haskell) have the benefit of allowing you, as the caller of a function, to dictate the return type of that function -- in fact, universal quantification is the default, and you have to go out of your way (e.g. with GADTs) to represent existential types.
This now leads us to a common pitfall for people coming to Haskell from OOP languages, where their intuition that "typeclass == interface" fails them:
-- The intent here is the same as earlier with "Stringable",
-- though in Haskell that would be "Show".
--
-- A beginner's intuition:
-- "Surely this reads 'somethingShowable is some type implementing the Show interface', right?"
somethingShowable :: (Show a) => a
somethingShowable = (42 :: Int)
A beginner will often think that the above should compile ("42 is an Int, and Int has a Show instance, so this should work, right? I just need to provide something that has a Show instance, right?"). Unfortunately, it doesn't compile. The above code is making the promise that it can provide a value of any type that the caller demands (so long as it implements Show), but here we're only providing an integer specifically.This bit of OOP pseudo-code captures what they we're going for:
func getStringableThing() Stringable {
return 42
}
A natural implication of this universal vs existential quantification distinction is this:Typeclass instances are resolved at compile time (except where you've gone out of your way to existentially type something), and can be specialized and inlined accordingly. Interfaces (a la OOP) are implemented via some sort of vtable mechanism, where dispatch is deferred until runtime (though a JIT can potentially do some inlining).
Interfaces (a la OOP) and typeclasses are implemented (and behave) quite differently, so I would argue that if Haskell were to replace its use of "class" (and "typeclass") with "interface" it would only confuse things further (as evidenced by the constant stream of beginners asking for help where they've made this exact conflation; for the record, I was one of these beginners 3 years ago).
The "they shouldn't call these (type)classes!" complaint is quite tiring: no one ever has a reasonable alternative to offer. At least (type)class makes some sense when you realize that "class" is meant in the mathematical sense of the word "class".
It's still confusing. And programming languages exist to first and foremost aid programmers, not to adhere to some abstract expression of mathematical perfection.
At the time Haskell hit the scene, the term Interface was free and could've been taken. But that's just one thought, I'm not a language designer...
There are cases where that can't be done at all (polymorphic recursion, when the recursion can't be proven to terminate at compile time, I'm sure there are other) and runtime dispatching is required, but that's more of an exception. In C++ that's implemented with explicit, semi manually implemented, type erasure wrappers.
edit: as I said, I'm not an Haskell programmer; maybe in idiomatic Haskell the cases where runtime dispatch is required are much more common.
Comparatively, imagine you have a problem for which you need ad-hoc, but statically resolvable, overloads. In C++, you typically reach for templates. In Haskell, you instead typically reach again for typeclasses.
This is what I mean when I say neither comparison is really closer than the other. Haskell couldn't replace typeclasses with templates, just as it couldn't replace them with objects.