[The q is a play on the title of this 2007 survey of Haskell.]
tl;dr I have a couple of connected questions about Haskell's overloading mechanisms. I'll ask first then explain why. I'm looking at the discussions when overloading was 'discovered' around 1988/1989, and imagining how would some of the alternative designs have worked out, if we knew then what we know now. (I'll maybe add more material/references if commenters ask.)
Is it essential for typeclasses to be able to bundle together more than one method? Or would it work to have each method in a separate class, with superclass constraints to link methods (as for example
Eqis a superclass of
If we have one method per class, do we really need classes? Or could we just express overloading direct over the methods?
1. One method per class?
A frequent complaint about the
Num is that it bundles together all arithmetic operators. But mathematical purity says we should have an Additive class, a Multiplicative class, etc, etc, with Additive being a superclass of Multiplicative.
If we make an
instance Num for some datatype, we must define an implementation for every operator whether or not it makes sense. (Or if you don't declare an implementation, the compiler gives you one anyway, that returns
undefined at run time.) What we want for operators that don't make sense over that datatype is a rejection at compile time.
Aside from Mathematical purity, there's a particularly down to earth problem/source of much puzzlement on StackOverflow:
Num includes method
fromInteger. There's a hidden call to
fromInteger built into every integer literal. (Which is to say that what looks like a literal in any other programming language is not a literal in Haskell.) Say your
Num instance is for arithmetic over vectors or matrixes. If you put
x :: Vector x = 10
Most newbies expect a compile fail: they forgot to wrap the literal in a constructor to turn it into a vector. Instead, it compiles and if you're lucky, fails at run time with
undefined; or perhaps happily executes and gives puzzling results.
Changing the design of
Num now is just too hard, so we live with it; but what if we could start afresh and separate concerns for the operators? Would we end up with each operator in a separate class?
And similar questions for the other
Prelude classes with multiple methods. (Plenty of them have a single method anyway. But for example there's grumbling from people who want a
2. Overload the methods; don't need no
The very first design for overloading section 1 of Wadler's 1988 memo -- and I mean before the early 1989 paper with Blott -- doesn't mention
class. It just wades right in and declares a bunch of operators as overloadable, with their type signature; then gives instances for them.
I read section 1 as setting up something of a straw man: at the end it points out some difficulties, which section 2 remedies (with
class). But I see some mis-steps in section 1, so the difficulties can be avoided without introducing a new entity into the language IMHO. (This is no criticism of Wadler: it's amazing how much he anticipated so early on.)
class Wadler draws straight from OOP. But oh woe!
Haskell classes are not very like OOP classes: there's no class inheritance/subtyping; there's no encapsulation of data; there's no information hiding. (Haskell can do all those things, but the concerns are separated into other mechanisms.)
And the confusion it causes learners coming to Haskell from OOP languages is immense. Perhaps Haskell could use another term.
But rather: with only one method per class, just cut out the middle man. We still implement as dictionary-passing (after all, the dictionary gets passed to a version of the method, as an extra invisible argument); the class gets type-erased anyway; we can attach the evidence-passing to the method rather than the class(?)
In type inference, we express constraints (wanted or given) as "needs an overload for
(+) at type
Vector", etc. (I won't deflect into pondering syntax: it would need method names appearing in type signatures; which would probably have caused palpitations in 1988; but nowadays we're happy to put types in terms with explicit type application; so why not vice versa?)
((+) a, (*) a, fromInteger a, div a) => a -> ato
Num a => a -> a). Of course none of these considerations is fundamental -- they don't affect the expressive power of the language at all -- but they are convenient nonetheless. $\endgroup$
divisive will have multiplicative and additive as superclasses, so we don't need those explicitly. And nothing to stop us creating a dummy method
numwith superclasses, um supermethods, all of the above. Attach the laws to
numif you like. Default implementations are of methods so yes we need a way to declare them, but not a class. IMO $\endgroup$
class]." for lots and lots and lots more things, too. :) $\endgroup$