To clarify my question, let me rephrase it in a more or less equivalent way:
Why is there a concept of superclass/class inheritance in Haskell? What are the historical reasons that led to that design choice? Why would it be so bad, for example, to have a base library with no class hierarchy, just typeclasses independent from each other?
Here I'll expose some random thoughts that made me want to ask this question. My current intuitions might be inaccurate as they are based on my current understanding of Haskell which is not perfect, but here they are...
It is not obvious to me why type class inheritance exists in Haskell. I find it a bit weird, as it creates asymmetry in concepts. Often in mathematics, concepts can be defined from different viewpoints, I don't necessarily want to favor an order of how they ought to be defined. OK there is some order in which one should prove things, but once theorems and structures are there, I'd rather see them as available independent tools.
Moreover one perhaps not so good thing I see with class inheritance is this: I think a class instance will silently pick a corresponding superclass instance, which was probably implemented to be the most natural one for that type. Let's consider a Monad viewed as a subclass of Functor. Maybe there could be more than one way to define a Functor on some type that also happens to be a Monad. But saying that a Monad is a Functor implicitly makes the choice of one particular Functor for that Monad. Someday, you might forget that actually you wanted some other Functor. Perhaps this example is not the best fit, but I have the feeling this sort of situation might generalize and possibly be dangerous if your class is a child of many. Current Haskell inheritance sounds like it makes default choices about parents implicitly.
If instead you have a design without hierarchy, I feel you would always have to be explicit about all the properties required, which would perhaps mean a bit less risk, more clarity, and more symmetry. So far, what I'm seeing is that the cost of such a design, would be : more constraints to write in instance definitions, and newtype wrappers, for each meaningful conversion from one set of concepts to another. I am not sure, but perhaps that could have been acceptable. Unfortunately, I think Haskell auto deriving mechanism for newtypes doesn't work very well, I would appreciate that the language was somehow smarter with newtype wrapping/unwrapping and required less verbosity. I'm not sure, but now that I think about it, perhaps an alternative to newtype wrappers could be specific imports of modules containing specific variations of instances.
Another alternative I thought about while writing this, is that maybe one could weaken the meaning of class (P x) => C x
, where instead of it being a requirement that an instance of C
selects an instance of P
, we could just take it to loosely mean that for example, C
class also contains P
's methods but no instance of P
is automatically selected, no other relationship with P
exists. So we could keep some sort of weaker hierarchy that might be more flexible.
Thanks if you have some clarifications over that topic, and/or correct my possible misunderstandings.
Monoid
, both mathematically and in Haskell, must be aSemigroup
(your Functor example is also a good one). Two, the problem of multiple type class definitions exists independently of inheritance. For example, you could make theApplicative
instance for lists use the normalap
and theZipList
pure
. Unfortunately, the laws we define for classes aren’t easily proved in standard haskell; however, breaking laws is an issue inheritance or not. – coleFunctor f => Applicative f
represents exactly that: "Only Functors can also be Applicative Functors". I am also curious about you saying "c selects an instance of p". There is no such 'selection". What it means is "For x to be a C, x first needs to be a P". There is no choice involved. – Sir4ur0nBut saying that a Monad is a Functor implicitly makes the choice of one particular Functor for that Monad.
But that's just an expression of the way things are. Once you have a Monad, there is one and only one sensible Functor instance, given byfmap f x = x >>= (return . f)
– Robin ZigmondMonad
as an independent class fromFunctor
(applicatives did not even exist), so we had to frequently writef :: (Monad m, Functor m) => ...
if we wanted to reuse functions requiring a functor. That felt silly sincefmap f x = x >>= return . f
makes any monad into a functor. – chi