Beginner error messages in C++ vs Haskell

posted on 2014-09-17 by Paul Starkey

Learning Haskell was excruciating. The error messages from the Haskell compiler ghc were way more difficult to understand than the error messages I was used to from g++. I admit I’m still a novice programmer: My only experience is a year of classes in C++ programming. But the Haskell compiler should do a better job generating error messages for beginners like me.

First we’ll see four concrete examples of ghc doing worse than g++, then Mike will talk about some ways to fix ghc’s error messages.

Example 1

Below are two equivalent C++ and Haskell programs. I’ve intentionally added some syntax errors:

/* C++ Code */ #include <iostream> using namespace std; int main () { int in = -1; cout << "Please choose 1 for a message" << endl; cin >> in; err-> if in == 1 { cout << "Hello, World!" << endl; } else{ cout << "Error, wrong choice" << endl; } return 0; }

{- Haskell Code -} main = do putStrLn "Please enter 1 for a message" num <- getLine if num == "1" then do putStrLn "Hello, World" err-> putStrLn "Error, wrong choice"

Alright, so the first notable difference is that the Haskell code is much shorter. It takes up roughly half the space that the C++ code does, yet they both output hello world when the correct number is entered.

Great!

Haskell already seems better, right?

Wrong!

Notice how I messed up the if statement in both programs. In the C++ version, I forgot the parentheses, and in the Haskell version I forgot the else . Both omissions are simple mistakes that I’ve made while learning these languages.

Now let’s see the error messages:

-- C++ Error -- main.cpp: In function 'int main()': main.cpp:15:5: error: expected '(' before 'in' main.cpp:19:2: error: 'else' without a previous 'if' Compilation failed.

-- Haskell Error -- [..]main.hs:19:1: parse error (possibly incorrect indentation or mismatched brackets) Failed, modules loaded: none.

Both error messages let the programmer know where the mistake happened, but the g++ message is far more helpful. It tells us how to fix the syntax error by adding some missing parentheses. Bam! Problem solved.

Now let us turn to ghc’s output. Okay, something about a parse error… might have indentation errors… and no modules loaded. Cool. Now I’ve never taken a compiler course, so I don’t know what parse error means, and I have no idea how to fix it. The error message is simply not helpful.

Example 2

Here’s another example of parsing errors.

/* C++ Code */ #include <iostream> using namespace std; int main() { err-> string in = "" cout << "Please enter a single word and get the string size back" << endl; cin >> in; cout << "The size of your word, \"" << in << "\", is " << in.length() << "!" << endl; return 0; }

{- Haskell Code -} err-> main = {-do-} putStrLn "Please enter a single word and get the string size back" num <- getLine let size = length num putStrLn $ "The size of your word, \"" ++ num ++ "\", is " ++ show size ++ "!"

As you can see, in the C++ I forgot to include a semicolon and in the Haskell I forgot the do in main. As a beginner, I’ve personally made both of these mistakes.

Here are the error messages:

-- C++ Error -- main.cpp:8:2: error: expected ',' or ';' before 'cout' Compilation failed.

-- Haskell Error -- [..]main.hs:4:13: parse error on input '<-' Failed, modules loaded: none.

C++ delivers a clear message explaining how to fix the error. Haskell, however, is not so helpful. It says there’s a parse error on the input operator. How should I know this is related to a missing do ?

Example 3

Next let’s see what happens when you call the built-in strlen and length functions with no arguments at all.

/* C++ Code */ #include <iostream> #include <cstring> using namespace std; int main (){ char input[256]; cout << "Please enter a word" << endl; cin >> input; err-> cout << "The size of your string is: " << (unsigned)strlen(); cout << "!" << endl; return 0; }

{- Haskell Code -} main = do putStrLn "Please enter a word" num <- getLine err-> let size = length putStrLn $ "The size of your string is: " ++ show size ++ "!"

Now let us see the different error messages that are produced:

-- C++ Error -- main.cpp: In function 'int main()': main.cpp:11:61: error: too few arguments to function 'size_t_strlen(const char*)' Compilation failed.

-- Haskell Error -- [..]main.hs:7:36: No instance for (Show ([a0]->Int)) arising from a use of 'show' Possile fix: add an instance declaration for (Show ([a0]->Int)) In the first argument of '(++)', namely 'show size' In the second argument of '(++)', namely 'show size ++ "!"' In the second argument of '(++)', namely '"\", is " ++ show size ++ "!"' Failed, modules loaded:none.

Once again, it appears that the C++ compiler g++ knew exactly what was wrong with the code and how to fix the error. It tells me that there are not enough arguments in my function call.

Wow, Hakell’s error message is quite the mouthful this time. I suppose this is better than just a parse error message, but I’m not sure what exactly ghc is even wanting me to correct. The error is simply too technical to help me.

Example 4

Next, we will look at what happens when you pass too many arguments to functions in both languages:

/* C++ Code */ #include <iostream> using namespace std; int main () { string in[256]; cout << "Please enter a single word to get the string size back" << endl; cin >> in; err-> cout << "The size of your string, \"" << in << "\", is " << (unsigned)strlen(in, in); cout << "!" << endl; return 0; }

{- Haskell Code -} main = do putStrLn "Please enter a single word to get the string size back" num <- getLine err-> let size = length num num putStrLn $ "The size of your string, \"" ++ num ++ "\", is " ++ show size ++ "!"

And the errors:

-- C++ Error -- main.cpp:16:78: error: too many arguments to function 'int newLength(std::string)' main.cpp:6:5: note: declared here Compilation failed.

-- Haskell Error -- Couldn't match expected type 'String -> t0' with actual type 'Int' The function 'length' is applied to two arguments, but its type '[Char] -> Int' has only one In the expression: length num num In an equation for 'size':size = length num num Failed, modules loaded: none.

The C++ error clearly explains how to fix the code, and I even understand the Haskell error this time. Both languages tell me that there are too many arguments. Yet the C++ error message tells me this without a bunch of technical jargon. So even when Haskell is actually helpful with its error messages, it still manages to hide what it wants the user to do.

Conclusion

To me, Haskell seems like a language only for experienced programmers because the errors are not user-friendly. How can I write code if a few simple mistakes cripple my progress? Haskell’s compiler ghc simply lags behind g++ in terms of useful error messages for beginners.

Mike’s Epilogue

I’ve created a patch for ghc that clarifies the specific error messages that Paul had trouble with (and a few related ones). In particular:

Anytime there is a parse error caused by a malformed if , case , lambda, or (non-monadic) let , ghc will now remind the programmer of the correct syntax. In the first example Paul gives above, we would get the much clearer: parse error in if statement: missing required else clause To help with the second example, anytime ghc encounters a parse error caused by a <- token, it now outputs the hint: Perhaps this statement should be within a 'do' block? The third example Paul points out comes from the type checker, rather than the parser. It’s a little less obvious how to provide good hints here. My idea is based on the fact that it is fairly rare for functions to be instances of type classes. The only example I know of is the Monad instance for (a->) . Therefore, if the type checker can’t find an instance for a function, the more likely scenario is that the programmer simply did not pass enough parameters to the function. My proposed change is that in this situation, ghc would output the hint: maybe you haven't applied enough arguments to a function?

This patch doesn’t completely fix ghc’s problem with poor error messages. For example, it doesn’t address Paul’s last point about type errors being verbose. But hopefully it will make it a little easier for aspiring haskellers who still aren’t familiar with all the syntax rules.