Desenvolvimento

24 nov, 2014

Mensagens de erros para iniciantes em C++ versus Haskell

Publicidade

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