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?
-
3This is a big topic. Rank 2 polymorphism is definitely the most important one, and rank N polymorphism follows naturally. The next one is up to your interest. GADTs and MPTCs/FDs can both be used to achieve some of the same results, so as an exercise, try doing the same thing with each. Existential types are useful, but as an exercise, try removing existential quantification from your types—my intuition tells me that 80% of the time, you're only using existential quantification because that's how you'd do it in Java/C++/Python, not because it's clearer code. – Dietrich Epp Apr 01 '14 at 18:28
-
As an exercise, explain why Rank 2 types are necessary to assure the safety of the ST monad. – Dietrich Epp Apr 01 '14 at 18:30
-
It's impossible to answer this question in a non-opinion based way, IMHO. Still, I would follow this order: scoped type vars, rank-n polymorphism, multiparameter type classes, GADTs, and the rest in any order. It helps learning these by using a library which exploits them: e.g. the `ST` monad is perfect to learn rank-2 polymorphism, as @DietrichEpp pointed out. – chi Apr 01 '14 at 18:53
-
IMO, you should just read for all of them what they roughly do (never mind if you don't understand it at first) and, later when you think "hey, that sounds like one of those `TypeFamilies` associated types would do the trick!" you learn the details. (Though, as Dietrich sais, existentials should be avoided, `OverlappingInstances` as well.) See [here](http://stackoverflow.com/questions/10845179/which-haskell-ghc-extensions-should-users-use-avoid?lq=1) for some advice. – leftaroundabout Apr 01 '14 at 19:02
-
I think OverloadedStrings is probably a pretty important one... – J. Abrahamson Apr 01 '14 at 23:23
-
@J.Abrahamson, that sounds more convenient than important. Less important than, say, the extension that allows newtypes to share class dictionaries with base types via deriving, so you don't pay for abstraction with speed and callus. – dfeuer Apr 02 '14 at 04:28
2 Answers
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.)

- 8,026
- 28
- 53
-
I thing that `StandaloneDeriving` has its best use when in GHCi. There are so many times I define a data type inline just messing with something and then realize that I forgot to add `deriving (Eq, Show)`. I can instead just do `:set -XStandaloneDeriving` and `deriving instance Show MyType`. – bheklilr Apr 01 '14 at 19:27
-
4What's so bad about `StandaloneDeriving`? It may be able to derive instances that you cannot derive with `data XXX ... deriving (...)` – bennofs Apr 01 '14 at 20:17
-
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.
-
I think it's interesting how different people's styles are. It seems like `RankNTypes` are something of an afterthought in both of these answers, whereas I feel I cannot code without them. – luqui Aug 13 '15 at 01:58
-
@luqui, it's possible that I use them more than I realize. They have some tendency to lurk unnoticed. How do you use them? – dfeuer Aug 13 '15 at 02:48