As anyone reading might know, I’ve recently been experimenting more with Haskell, which is a strongly (and statically) typed language. Haskell uses a feature called type classes to allow polymorphic functions, where a type class is essentially a list of functions that can be implemented in a type specific way for all instances of the class.
I like the concept of type classes, and from what I’ve read the type class solution is a lot better than approach to polymorphism offered by some other functional languages like ML. But there are a few frustrating problems with them as they currently stand.
The first problem is that type classes force you to divide up all your polymorphic functions, and there may not be a unique way to do that. For example, Haskell’s Num type class contains, among other functions, (+), (-), and (*). The problem with this is that there are number-like things for which you might not want to define two or more operations. For example, an unadorned mathematical group has exactly one bivalent operation.
Of course, you could get around this by having any functions you don’t want to define for your type throw an error, but this seems less than ideal since you’re breaking the implicit promise to implement the interface specified by the type class. A solution to this would be to not have multi-function type classes unless you’re really sure that the functions will always go together, but that’s the dreaded ‘ad-hoc polymorphism’ that Haskellers don’t seem to like very much.
The second problem is that you can only have exactly one implementation of a type-class for a given type in your entire program. And there doesn’t appear to be any way to hide or not import a type-class instance, so if you use a library and don’t want to modify its source you’re also forced to accept all its type-class instances. An example of a problem with this might be the QuickCheck library – the generation of random test sets for a type is enabled by type-class membership, but it doesn’t seem beyond the bounds of possibility to want to be able to generate random type values in different ways.
One way around this is to use newtype to create a new type and then reimplement the type classes you want to change, but:
- This only works for types with exactly one data constructor
- This means that you can’t use any non-polymorphic functions that worked with the old type with your new type
The final problem is that, given that type-classes are supposed to be the Haskell solution to polymorphism, they’re not used anywhere near enough. For example, the Haskell Prelude defines a function lookup, which searches an association list and returns the value associated with a key. The modules Data.Map and Data.IntMap also contain functions called lookup, but because their lookups clash with the Prelude (and each other) you have to import the modules qualified. The list of modules you have to import qualified to avoid clashes seems to be quite long in Haskell, because there are far too many type-specific functions and insufficient use of type-classes. And this is unfortunate, because too many type-specific functions means that code re-usability suffers.
I think it’s the final problem that’s the biggest one. Type-classes do have their problems, but that doesn’t mean they’re all bad. The inability to easily write generic code because people aren’t using the polymorphism facilities of the language seems worse than enduring the problems with the type-class approach.
Posted by chrisdb on 2010-09-13
Comments