Search code examples
haskelltypeclassstandard-library

Alternative implementations of Haskell's standard library type classes


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:

  1. 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?

  2. How drastically a change in this would break existing code?

  3. Are there alternative implementations of the basic type classes (particularly arrows, monads, applicative, etc...) around that implement the "right" set of class dependencies?


Solution

  • 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.