Mensagens de erros para iniciantes em C++ versus Haskell
Vamos ver quatro exemplos concretos do GHC fazendo pior que g++. Então, serão mostradas algumas maneiras de corrigir as mensagens de erro do GHC.
Aprender Haskell era insuportável. As mensagens de erro do compilador GHC Haskell eram muito mais difíceis de entender do que as do g++ com que estava acostumado. Admito que ainda sou um programador novato: minha única experiência é um ano de aulas de programação em C++. Mas o compilador Haskell deve fazer um trabalho melhor gerando mensagens de erro para iniciantes como eu.
Primeiro, vamos ver quatro exemplos concretos do GHC fazendo pior que g++. Aí então o Mike vai falar sobre algumas maneiras de corrigir as mensagens de erro do GHC.
Exemplo 1
Logo abaixo estão dois programas equivalentes em C++ e em Haskell. Eu adicionei intencionalmente alguns erros de sintaxe:
/* 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"
Tudo bem, então a primeira diferença notável é que o código Haskell é muito mais curto. Ele leva cerca de metade do espaço que o código C++, ainda que ambos liberem saídas hello world, quando o número correto é inserido.
Ótimo!
Haskell já parece melhor, certo?
Errado!
Observe como eu estraguei todo o comando if em ambos os programas. Na versão em C++, eu esqueci os parênteses e, em Haskell, eu esqueci o resto. Ambas as omissões são erros simples que cometi enquanto aprendia as linguagens.
Agora vamos ver as mensagens de erro:
-- 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.
Ambas as mensagens de erro mostram ao programador onde o erro aconteceu, mas a mensagem do g++ é muito mais útil. Ela nos diz como corrigir o erro de sintaxe, acrescentando alguns parênteses perdidos. Bingo! Problema resolvido.
Agora vamos voltar para a saída do GHC. Ok, algo sobre um erro de interpretação… Pode haver erros de indentação e nenhum módulo carregado. Beleza. Eu nunca fiz um curso de compilador, então eu não sei o que significa parse error e não tenho ideia de como corrigi-lo. A mensagem de erro simplesmente não é útil.
Exemplo 2
Aqui está outro exemplo de erros de interpretação.
/* 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 ++ "!"
Como você pode ver, no C++, eu me esqueci de incluir um ponto e vírgula e no Haskell me esqueci do do no principal. Como um bom novato, cometi pessoalmente esses dois erros.
Abaixo estão as mensagens de erro:
-- 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.
O C++ oferece uma mensagem clara explicando como corrigir o erro. O Haskell, no entanto, não é tão atencioso. Ele diz que tem um erro de interpretação no operador de entrada. Como eu poderia saber que isso está relacionado a um do faltando?
Exemplo 3
Em seguida, vamos ver o que acontece quando você chama as funções embutidas strlen e length sem nenhum argumento.
/* 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 ++ "!"
Vamos ver agora as diferentes mensagens de erro que são produzidas:
-- 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.
Mais uma vez, parece que o compilador g++ de C ++ sabia exatamente o que estava errado com o código e como corrigir o erro. Ele me diz que não existem argumentos suficientes na minha chamada de função.
Uau, a mensagem de erro do Hakell é bem mais completa agora. Acho que isso é melhor do que só uma mensagem de parser error, mas não tenho certeza do que exatamente o GHC ainda está querendo que eu corrija. O erro é simplesmente muito técnico para me ajudar.
Exemplo 4
Em seguida, vamos ver o que acontece quando você passa muitos argumentos para as funções em ambas as linguagens:
/* 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 ++ "!"
E seus respectivos erros:
-- 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.
O erro de C++ explica claramente como corrigir o código, e eu até entendo o erro de Haskell desta vez. Ambas as linguagens me dizem que existem muitos argumentos. No entanto, a mensagem de erro do C++ me diz isso sem um monte de jargão técnico. Assim, mesmo quando Haskell é realmente útil com suas mensagens de erro, ele ainda consegue esconder o que quer que o usuário faça.
Conclusão
Para mim, Haskell parece ser uma linguagem apenas para programadores experientes, porque os erros não são amigáveis para o usuário. Como posso escrever o código, se alguns erros simples inviabilizam o meu progresso? O compilador GHC do Haskell simplesmente fica atrás do g++ em termos de mensagens de erro úteis para iniciantes.
Epílogo do Mike
Eu criei um patch para o GHC que esclarece as mensagens específicas de erro com as quais o Paul tinha problemas (e algumas relacionadas). Em particular:
- Sempre que houver um erro de análise causado por um mal formado if, case, lambda, ou let mal escrito, o GHC vai lembrar agora o programador da sintaxe correta. Gostaríamos de deixar mais claro o primeiro exemplo que Paul apresentou acima:
parse error in if statement: missing required else clause
- Para ajudar com o segundo exemplo, toda vez que o GHC encontra um erro de análise causado por um token <-, ele agora exibe a dica:
Perhaps this statement should be within a ‘do’ block?
- Paul salienta que o terceiro exemplo vem do verificador de tipos, em vez do analisador. É um pouco menos óbvio como fornecer boas dicas aqui. Minha ideia é baseada no fato de que é razoavelmente raro para funções ter instâncias de classes. O único exemplo que conheço é a instância Monad para (a->). Portanto, se o verificador de tipos não consegue encontrar uma instância para uma função, o cenário mais provável é que o programador simplesmente não transmita parâmetros suficientes para a função. Minha proposta de mudança é que, nessa situação, GHC gere a dica:
maybe you haven’t applied enough arguments to a function?
Esse patch não corrige completamente o problema do GHC com mensagens de erro pobres. Por exemplo, ela não aborda o último ponto do Paul sobre os tipos de erros sendo verbosos. Mas esperamos que ele torne tudo um pouco mais fácil para os aspirantes a programar em Haskell que ainda não estão familiarizados com todas as regras de sintaxe.
***
Artigo de autoria de Paul Starkey. Traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em https://izbicki.me/blog/error-messages-in-ghc-vs-g%2B%2B.html







