I've seen many people complaining about some of the type classes from the standard library saying things like "Monad should require Functor" or even "Monad should require Applicative", "Applicative should require Pointed", "Num shouldn't require Show", etc, So, I have some questions:
Are there arguments for the way the tree of type class dependencies have those "flaws" perceived by the community or is this just the result of how things were done historically?
How drastically a change in this would break existing code?
Are there alternative implementations of the basic type classes (particularly arrows, monads, applicative, etc...) around that implement the "right" set of class dependencies?
Haskell classes are roughly similar to a Java interface. Like an interface declaration, a Haskell class declaration defines a protocol for using an object rather than defining an object itself.
A typeclass is a sort of interface that defines some behavior. If a type is a part of a typeclass, that means that it supports and implements the behavior the typeclass describes. A lot of people coming from OOP get confused by typeclasses because they think they are like classes in object oriented languages.
An interface in the Java programming language is an abstract type that is used to specify an interface (in the generic sense of the term) that classes must implement. These two looks rather similar: type class limit a type's behavior, while interface limit a class' behavior.
What's a typeclass in Haskell? A typeclass defines a set of methods that is shared across multiple types. For a type to belong to a typeclass, it needs to implement the methods of that typeclass. These implementations are ad-hoc: methods can have different implementations for different types.
Beyond backwards compatibility, there are some (mostly cosmetic, but tedious to deal with) issues due to the fairly simple way that Haskell treats type classes:
No upward implicit definition: Monad
is fully defined by just pure
and (>>=)
; fmap
and (<*>)
can be written in terms of those. In a "proper" hierarchy, each instance would need to be written out. It's not too bad in this case but as the granularity increases so do the number of instances that each add some small function. It would simplify things substantially if class definitions could supply default implementations for superclass functions in terms of their own functions, so that functions like (>>=)
which fully subsume multiple functions in superclasses can serve as a definition for the entire relevant hierarchy.
Context bloat: Insofar as there's a "reason" that Num
requires Show
and Eq
, it's because printing and comparing equality of numbers is pretty common. These are strictly orthogonal, but separating them means that functions doing all three things now have to specify all three classes in their type. This is technically a good thing but, again, as granularity increases so will function type signatures.
Monolithic dependencies: A hierarchy of type classes and their superclasses can be added to, but not altered or replaced. If a piece of code feels the need to substitute its own version of some common type class--say, using something like this to replace Monad
--the hierarchy is cut off at that point; compatibility with code using other definitions of Monad
must be provided manually to some degree, and any type classes built on top of the other definition have to be reimplemented or translated even when reliant on only a subset of behavior that both definitions share.
No clearly correct hierarchy: As implied by the above, there are choices to be made in the granularity of the classes. For instance, does Pointed
really need to exist, or does just Applicative
suffice? There's really no answer here that's universally ideal, and there shouldn't have to be.
I suspect that efforts to replace the existing type classes would be better served by first tackling the above issues, after which replacing the type classes would be far less painful, or even little more than a formality. The type class synonyms proposal that @luqui mentions, for instance, would be a major step in this direction.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With