Arquivo de dezembro \19\UTC 2006

Nada de errado com tipagem estática

Tipagem estática quer dizer que todos os tipos estão determinados em tempo de compilação e que não mudam durante a execução do programa. Não deve haver problema nenhum com o fato do compilador verificar todos os tipos do seu programa antes de gerar um executável, certo? Isso pode até ser bom. Não há substituto para uma estrutura bem projetada e boa cobertura de testes, mas ter verificação de tipo durante a compilação pode mesmo evitar erros.

Como muita coisa, tipagem estática é uma faca de dois gumes e este é apenas um lado da moeda. O outro lado são as linguagens que exigem que o programador ajude o compilador a descobrir os tipos. Tudo para que eles possam dizer ao programador ‘Não, você não parece ter errado o tipo de nenhuma expressão’. Na presença de uma dessas linguagens, deveriam ser disparados alguns alarmes e deveria haver uma brigada de forças especiais dedicada especificamente a alertar os mais desavisados. Afinal de contas, as máquinas deveriam ajudar a nós e não nós a elas.

A realidade é que, se isso acontecesse, seria um mundo cheio de alarmes porque esse tipo de linguagem está em toda parte. Mas essa onipresença não faz com que ser forçado a escrever Integer no início de uma declaração como Integer x = 3 deixe de ser quase um insulto à inteligência. Se x é igual a três e três é um inteiro, é óbvio que x também é um inteiro. A declaração de tipo é só repetição. Repetição é uma coisa em que computadores são bons e pessoas são extremamente ruins. Pessoas ficam entediadas e querem desafios, computadores não precisam disso.

O nome que os pesquisadores arrumaram para esse tipo de repetição é inferência de tipos e deveria ser feita por toda linguagem e compilador sempre que possível. Mais: elas deveriam ser projetadas para que sempre fosse possível inferir tipos. Poupar a paciência dos programadores é sempre uma boa idéia e compiladores que precisam de tudo mastigado não fazem isso.

O raciocínio pode até ser estendido para expressões mais complexas. Aqui há uma definição de função em uma linguagem fictícia (que muito provavelmente é sintaxe válida em alguma linguagem real):

fac(n) {
    if (n == 0) return 1;
    else return n * (fac( n - 1));
}

Temos uma função fac que recebe um n, que por sua vez é usado em duas expressões dentro da definição da função. Na primeira, ele é comparado com zero e, na segunda, é subtraído de um. Portanto, n é algo que pode ser comparado e subtraído: um número. Isso faz da nossa função uma função sobre números. Mas, para falar um pouco de matematiquês, qual é o contra-domínio da função? Eu sei que devo passar um número para ela, mas o que vou receber em troca?

Essa é fácil, é só ver o que ela retorna. Temos duas expressões que retornam valores, a primeira retorna o valor um (um número). Isso faz de fac uma função de número para número, mas o tipo da segunda expressão de retorno precisa coincidir com o da primeira para que o código esteja consistente. Como n é um número que está sendo multiplicado por outro número (o fatorial do número anterior a ele), os tipos batem.

Estes dois exemplos são bem simples, mas inferir tipos realmente não é muito complicado. Podemos continuar aumentando a complexidade das expressões que ainda será possível inferir tipos. Tudo isto pode ser feito sem que o programador precise prover informação de tipos e em tempo de compilação. Por isso inferência de tipos é diferente de duck typing. Não quer dizer somente que o programador não precisa declarar os tipos, mas que além disso ele não vai precisar esperar até executar o programa para detectar os erros.