Over the last few days, we’ve looked at a few extensions that can extend the notion of type classes in Haskell. First, we saw that nullary type classes remove the requirement that a type class varies over a single type by allowing it mention no types at all, and yesterday we saw how type families can be used to associate more types against a single type. Today, we’re going to revisit yesterdays example and use the multi-parameter type classes extension.
The extension does just what it says on the tin - with
MultiParamTypeClasses enabled, GHC removes the constraint that a type class can mention only a single type. Now, we’re able to have our type class mention multiple types at once. Lifting this constraint has significant consequences; if we think of a type class over one type as modelling a set of types, whereas multiple types now let us model relations between types. The latter is interesting, though beyond the scope of this article. Interested readers are pointed to Oleg Kiselyov’s home page - which is full of mind bending tricks with type classes!
Yesterday, we looked at a traditional example around type classes - modelling the class of types that represent mutable variables. We used type families to associate the type of monad with each mutable variable, reaching the following API:
However, the API that we ended at is a little obtuse - those types take quite a bit of mental parsing to understand. Conceptually, we can think of mutable variables as having a relationship between types - the type of a mutable variable is related to the type of its monad. Using
MultiParamTypeClasses, we can encode just this idea - we simply vary the type class over both the variable type and its monad:
This API is much easier to understand! Furthermore, because the type class itself mentions the type of monad, using this type in our programs is straightforward. We can port over yesterdays example with only changes to the type:
I’m sure you’ll agree, that’s a much more manageable type. All that is left now is to provide instances for our type class:
Again, very little has changed from yesterdays code here - we just move the type of monad up to the type class instance declaration, rather than using an associated type.
So far I’ve put the extension in a great light, but there is a caveat: the use of multi-parameter type classes can lead to ambiguity during type checking. This can be a huge problem when writing large applications, as it means we now have to annotate our programs extensively.
To look at this problem in more detail, let’s look at using the
storePresents function we wrote earlier. If we build a store out of a list of
Presents as an
IORef and then query for the contents of the
IORef, something perculiar seems to happen:
What would you expect the type of this function to be? We’ve chosen
IORef as our store, and
IORefs are associated with the
IO monad, so we have
ex :: [Present] -> IO [Present], right? Let’s see what GHCI makes of it:
.> :t ex ex :: (Store IORef m, Monad m) => [Present] -> m [Present]
That’s odd! GHCI clearly knows that the variable type itself is an
IORef, but that’s not enough information to determine type of monad. For example, another equally valid definition of
Store IORef would be:
The problem we’re encountering is that multi-parameter type classes don’t add any information to the type inference engine - because knowing one type doesn’t let you know anything about the other types. However, we needn’t abandon hope here - this problem can be solved, it just needs another extension (oh, of course!).
This post is part of 24 Days of GHC Extensions - for more posts like this, check out the calendar.
You can contact me via email at firstname.lastname@example.org or tweet to me @acid2. I share almost all of my work at GitHub. This post is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.
I accept Bitcoin donations:
14SsYeM3dmcUxj3cLz7JBQnhNdhg7dUiJn. Alternatively, please consider leaving a tip on