Ever seen this in a library,
and thought, “Shenanigans! Why not just have this?”
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
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
_ >> return () :: Writer String (), the type of
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.1 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
Hope this was helpful!
Actually, it’s a natural consequence to grokking how instance resolution works (but calling it a “trick” makes for a catchy title).↩