Haskell: GHC's error messages explained

 GHC (Glasgow Haskell Compiler) is the current de-facto standard and the most used Haskell compiler. It is also reputed with having hard to read error messages. This post will aim to explain a few of its errors. It will also (mostly implicitly) aim to show you how explicit type signatures can really help you write better code.


Error #1: Occurs check: cannot construct the infinite type: a ~ [a]

I'm sure many people have seen this one. This error can be created using ghci:
This is a very simple piece of code and is not practical or elegant, but it will suffice for this example. Before we go into the details of the error, it is helpful to notice a few things:
  • x has the type a
  • foo is expected to return the type [a]
  • The action of foo returning x is invalid because x is of type a while foo expects it to be of type [a]
After the last point, its clear an error should be raised because type a cannot be type [a]. But why? Can't a be specialized to type [a]? No, it can't for various reasons. If a is specialized to type [a], then the a in type [a], can be expanded to [[a]] which can be infinitely expanded, hence the error message. This is a great example of how explicit type signatures can increase safety (and possibly drive you nuts).

Error #2: 'a' is a rigid type variable

Another very common error. This error can also be created using ghci:
Again a very simple piece of code. It should be obvious why this doesn't type check (++ takes two lists as arguments which were not supplied), but the "rigid variable" part can be confusing. As before, let's take a closer look at the code:
  • x is of type a
  • ++ expects two lists of type a
  • The action of using ++ on x twice is invalid because x is of type a while it is expected to be of type [a]
The last point makes it clear that an error should be raised because the type a cannot be type [a]. But why? Why can't a be specialized to type [a]? Imagine if instead of passing a list of something you passed an Int. How would  ++ be used on the Int (remember that ++ expects to list of type a)? It can't, so it raises the error that you see.

Another question a few people might have is "What does rigid mean? Isn't a supposed to be polymorphic (can be any type) which is the opposite of rigid?". You're right that it is the opposite of rigid, but that is on the caller's side, not the definition side.

This code could be easily fixed by changing the function's type signature:

Error #3: No instance for (Foo a) arising from a use of 'foo'

This is arguably the most made beginner mistake. As always this error can be created using ghci:
Let's notice a few things before digging into the error:
  • x and y are of type a
  • + has the type signature Num a => a -> a -> a, which means that the type a has to have a Num instance. You can read up on instances (and a few other helpful things) here
So why do we get an error? Because x and y are not constrained by Num. So the error is justified because you can't expect to pass in two strings and get them added up with +!

This code could easily be fixed in many different ways:
Notice the difference in the type signatures. As you can see, the last method explicitly states that it will only take in Ints as arguments, which means it cannot accept FloatsDoubles, etc.; The first method uses type constraints and is more flexible (but not necessarily better) because it can take in IntsIntegers[1]FloatsDoubles, and Words, and any other instance of Num.

Error #4: Could not deduce (Foo a) arising from a use of 'foo'

As always this error can be created using ghci:
If you think about this for a while the reason for this error is self-evident. It is very similar to the previous error. read doesn't know what type to read the string as. It could be read as an IntFloatWord, etc.

This can easily be fixed by adding en explicit type signature like so:
 

Error #5: Ambiguous type variable `a0' arising from a use of 'foo'

Here is the large, scary error made using ghci:
This uses type classes, so if you don't know what they are take a look here. Before going into detail about the error, let's observe a few things about the code:
  • foobar takes something of type a and returns something of type a
  • Int has an instance of FooBar (which means you can use the function foobar on an Int)
So what is the problem with calling foobar 10? The problem is... 10 isn't an Int. Wait, what? 10 is not an Int? Yep. If you type :t 10 into ghci (:t gets the type of whatever you pass in), you'll get Num p => p as its type. That means it's not an exactly an Int, just an instance of Num (I hope this doesn't sound too confusing), which means depending on the context, it can be any of its instances (IntIntegerFloat, etc). The problem here is it doesn't know what instance to be.

This could be fixed by telling it what to be by adding an explicit type annotation, just as the error message suggested (notice the call to foobar):

Conclusion

I hope you learned a thing or two about GHCs error messages and how to handle them. Moral of this post: Explicit type signatures = More errors caught = Better code = Happy coding!


Popular posts from this blog