Which GHC type system extensions should I try to learn first?
GHC has a whole zoo of type system extensions: multiparameter type classes, functional dependencies, rank-n polymorphism, existential types, GADTs, type families, scoped type variables, etc., etc. Which ones are likely to be easiest to learn about first? Also, do these features fit together in some way, or are they all pretty much separate ideas useful for entirely different purposes?
A good one to learn early on is ScopedTypeVariables
, because they are very useful for debugging type issues in a function. When I have a baffling type error, I temporarily add type declarations on each of the expressions in the function. (Often you'll need to break up some of the expressions to see what's really going on.) That usually helps me determine which expression has a different type than I expected.
TypeFamilies
are more powerful than MultiParamTypeClasses
, so you don't really need the latter. When working with type families, you usually need to enable FlexibleContexts
and FlexibleInstances
as well, so that's three pragmas you'll learn for the price of one. FunctionalDependencies
is generally used with MultiParamTypeClasses
, so that's one you can ignore for now.
GHC is pretty good at telling you when you need to enable Rank2Types
or RankNTypes
, so you can postpone learning more about those until a little later.
Those are the ones I'd start with.
EDIT: Removed comment about avoiding StandaloneDeriving
. (I was thinking of orphan instances.)
Note that I've worked with Haskell some more, I've developed some of my own opinions on the matter. mhwombat's suggestion of ScopedTypeVariables
was a good one. These days it's usually the first thing I type when I start writing a Haskell module. Whenever code gets a bit tricky, I like to have lots of type signatures to help me see what I'm doing, and this extension lets me write ones I otherwise couldn't. It also can improve type errors dramatically. It also seems to be nearly essential when using other type system extensions.
I didn't really appreciate GADTs too much until I learned a bit about dependently typed programming languages. Note I think it's awesome how they can serve as proof objects, and how they can be constrained by type indices.
GADTa work very well with DataKinds
, which produces fun type indices like lists and Booleans. I can now do things like express that an indexed list is as long as a tree is tall, without driving myself crazy using higher-order nested types.
I still haven't explored multiparameter type classes and functional dependencies much yet. I have, however, come to appreciate Edward Kmett's reflection
library, which uses them in its interface.
I have learned a healthy respect for overlapping and incoherent instances, by which I mean I never use them. The overlapping ones feel a bit like macro programming with worse error messages, while the incoherent ones are insane.
RankNTypes
is powerful indeed. It's one of those things that's rarely needed, but when needed is really essential.