Earlier today I found myself writing the same sequence of long constraints on my type-signatures over and over again in a Haskell program I was working on. The program is still in flux, so that means the constraints may still change. As all of the functions call each-other, they need to have a similar set of constraints on their type signatures.
This means as the program evolves, I'll need to make a lot of similar changes all over the source file. I'm pretty lazy, so that doesn't sound like fun to me. At first I thought I could do something like this with regular type synonyms, but that requires all functions to share their entire type, not just a set of constraints.
There are a few ways I could've solved this problem:
Don't use type signaturesI'm not using any fancy type-level hacks, so the compiler doesn't really need them. But I like having them to prove to myself that I really do know what my code does, and to provide better error messages.
CPP MacrosI haven't tried this one - I just thought of it while writing this
Type ClassesWhich is what this post is about
Let's say I have a number of functions whose type signatures are along the lines of:
> myFunc :: (Eq b, Show b, MyClass b, MyOtherClass b) => Int -> String -> b -> b
and I don't like typing the (Eq b, Show b, MyClass b, MyOtherClass b) part over and over again. I can define a typeclass which captures all of those constraints:
> class (Eq b, Show b, MyClass b, MyOtherClass b) => MyConstraints b
along with a rule to populate the class:
> instance (Eq b, Show b, MyClass b, MyOtherClass b) => MyContraints b
I can now re-write the type-signature for myFunc as follows:
> myFunc :: MyConstraints b => Int -> String -> b -> b
This works for the following reasons:
- Memebership in the class "MyConstraints" implies membership in all of the other classes, due to the constraint on the class defintion.
- Every type which satisfies the constraints is a member of the "MyConstraints" class.
As another check, if you load the module defining myFunc into GHCi and ask for its type at the interactive prompt, it will report it as
myFunc :: (Eq b, Show b, MyClass b, MyOtherClass b) => Int -> String -> b -> b
Which is exactly what I wanted.