The constraint trick for instances

Ever seen this in a library,

and thought, “Shenanigans! Why not just have this?”

Me too!

I only learned of this solution relatively recently, and I know experienced Haskellers who also only understood this recently or still don’t. Hence this quick write up. Here’s the thought process.

We’re writing a trivial pretty printer and we’re using Writer . We write things like:

Quality. But writing tell every time is so boring! How about we use the IsString class so that we can just write the string literals like this?

Let’s write the IsString instance:

What do you say, GHC?

Couldn’t match type ‘a’ with ‘()’ ‘a’ is a rigid type variable bound by the instance declaration

Oh. Good point. The type of our tell call results in Writer String () . A small set back. Fine, let’s change the instance declaration to just be () :

GHC loves it!

Let’s try using it:

This displeases me. But it adds up given the type of (>>) :

In _ >> return () :: Writer String () , the type of _ is Writer String a , so we really need an IsString instance that matches that. But we already tried that. Oh, woe!

Some people reading this will be nodding in recognition of this same problem they had while writing that perfect API that just won’t work because of this niggling issue.

Here comes the trick. So let’s go back to a basic instance:

Suppose I replace this instance with a new instance that has constraints:

Question: Does that change whether GHC decides to pick this new version of instance over others that may be available, compared to the one above? Have a think.

The answer is: nein! The constraints of an instance don’t have anything to do with deciding whether an instance is picked from the list of instances available. Constraints only apply after GHC has already decided it’s going with this instance.

So, cognizant of this obvious-after-the-fact property, let’s use the equality constraint that was introduced with GADTs and type families (enabling either brings in ~ ):

Let’s try it:

This instance is picked by GHC, as we hoped, because of the a . The instance method also type checks, because the constraint applies when type checking the instance methods, just like if you write a regular declaration like:

That’s it! This crops up in a number of my own libraries and knowing this really helped me. Here is a real example from my lucid library:

Hope this was helpful!

© 2015-06-19 Chris Done