I chose to provide ordinary overloading and multi-type type-classes in my language Felix.
I consider (open) overloading essential, especially in a language which as a lot of numeric types (Felix has all C's numeric types). However unlike C++ which abuses overloading by making templates depend on it, Felix polymorphism is parametric: you need overloading for templates in C++ because templates in C++ are badly designed.
Type classes are provided in Felix as well. For those that know C++ but don't grok Haskell, ignore those who describe it as overloading. It isn't remotely like overloading, rather, it is like template specialisation: you declare a template which you don't implement, then provide implementations for particular cases as you need them. The typing is parametrically polymorphic, the implementation is by ad hoc instantiation but it is not intended to be unconstrained: it has to implement the intended semantics.
In Haskell (and C++) you cannot state the semantics. In C++ the "Concepts" idea is roughly an attempt to approximate the semantics. In Felix, you can approximate the intention with axioms, reductions, lemmas and theorems.
The main, and only advantage of (open) overloading in a well principled language like Felix is that it makes it easier to remember library function names, both for the program writer and for the code reviewer.
The primary disadvantage of overloading is the complex algorithm required to implement it. It also doesn't sit very well with type inference: although the two are not entirely exclusive, the algorithm to do both is complex enough the programmer probably wouldn't be able to predict the results.
In C++ this is also a problem because it has a sloppy matching algorithm and also supports automatic type conversions: in Felix I "fixed" this problem by requiring an exact match and no automatic type conversions.
So you have a choice I think: overloading or type inference. Inference is cute, but it is also very hard to implement in a way that properly diagnoses conflicts. Ocaml, for example, tells you where it detects a conflict, but not where it inferred the expected type from.
Overloading is not much better, even if you have a quality compiler that tries to tell you all the candidates, it can be hard to read if the candidates are polymorphic, and even worse if it's C++ template hackery.
(+)
has only one type:Num a => a -> a -> a
, while C#'s+
has multiple signatures. Function overloading breaks an important property of type system: every expression has one most general type. Working with C#, I'm frustated that I cannot have a generalized function over summable types, precisely because (a, b) => a+ b does not have type assignment that accepts every usage of the type. – Xwtek Dec 02 '21 at 09:40auto
the next best thing that can be done. – Xwtek Dec 02 '21 at 09:47