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