Programação, Scala

“Type erasure” ou remoção de informação de tipos em Scala

Você talvez tenha se deparado com o termo “type erasure”, ou remoção de informação de tipos, em discussões sobre certas linguagens. O termo pode soar inicialmente críptico: afinal, tipos são “removidos” da linguagem?

Na verdade a informação de tipos genéricos é apagada, sim, no código compilado. E o motivo dessa remoção tem origem na separação que existe entre a máquina virtual Java(JVM), e as linguagens que são compiladas para a JVM.

Até Java 1.5
No início dos anos 2000, a Sun decidiu introduzir tipos genéricos à linguagem Java versão 1.5. Esta funcionalidade já existia em C++ e o código produzido em Java sem o uso de genéricos torna-se um verdadeiro mar de conversões de tipos. Por exemplo: um ArrayList de Java 1.4 tem em sua assinatura o tipo Object para sua lista de itens. Como Object é superclasse de todos os outros tipos referenciais(objetos), o ArrayList poderia armazenar qualquer objeto, exceto tipos primitivos não referenciais(int,long,byte,etc).

Porém, Java é uma linguagem fortemente tipada, logo para que o container seja útil o tipo correto dos objetos armazenados no mesmo deve ser recuperado após estes serem armazenados como Objects genéricos. Para tal função usávamos type casts, ou conversões de tipos. Vejamos um exemplo de código estilo pré-Java-1.5:
ArrayList primos = new ArrayList();
primos.add(new Integer(2));
primos.add(new Integer(3));
primos.add(new Integer(5));
primos.add(new Integer(7));
Integer c = (Integer)primos.get(1);

Como podemos ver, o retorno de cada chamada a get no ArrayList devia ser acompanhada de uma conversão para o tipo armazenado no ArrayList. No caso específico acima, Integer.

Ao compilar esse código usando um JDK atual, o compilador avisará que o código é inseguro, porque o tipo obtido com primos.get(1) não foi verificado pelo compilador. Na verdade, sabemos que ArrayList armazena Object’s e um desses Object’s foi obtido do ArrayList e convertido para Integer sob nosso comando. O compilador não tem a informação de que o objeto armazenado no ArrayList é do tipo Integer, pois este armazena apenas Object.

Do Java 1.5 em diante
A linguagem Java solucionou o problema das conversões inseguras de tipos de retorno de funções através dos genéricos. Ao instanciarmos um ArrayList, informamos também o tipo de objeto que será armazenado no ArrayList, assim:
ArrayList<Integer> primos = new ArrayList<Integer>();
primos.add(new Integer(2));
primos.add(new Integer(3));
primos.add(new Integer(5));
primos.add(new Integer(7));
Integer c = primos.get(1);

Agora o ArrayList retorna itens apenas do tipo Integer, e o método get não requer mais a conversão de tipos. Além disso, o compilador tem a informação de tipo, e pode verificar, em tempo de compilação, se os objetos que estão sendo adicionados e removidos do ArrayList são mesmo do tipo correto.

Finalmente: Type erasure
Contudo, um fato pode surpreender o leitor: a JVM não possui tipos genéricos! Tudo é feito pelo compilador: os bytecodes gerados após a compilação não incluem informação dos tipos parametrizados que foram informados no código-fonte. O ArrayList<Integer> será apenas um objeto ArrayList, armazenado na memória heap, sem qualquer informação de tipo embarcada no código binário. O tipo Integer foi apagado do código fonte após o compilador efetuar as devidas verificações e conversões de tipos. Ou seja, o primos.get(1) que não empregou a conversão (Integer) sofreu conversão no código traduzido pelo compilador, e o tipo Integer passado como parâmetro foi simplesmente removido.

Como o leitor já deve ter deduzido, a este processo damos o nome de “type erasure”.

Type erasure em Scala
Scala, como todos sabemos, funciona encima da JVM, e é capaz utilizar transparentemente todo o código legado de Java. Os genéricos de Java se tornam genéricos em Scala, modificando apenas a sintaxe. Em Scala, usamos os [colchetes] para informar tipos genéricos, em Java usamos <maior e menor que>. Por utilizar a JVM, em Scala também ocorre a remoção de informação de tipos durante o processo de compilação.

A remoção de informação de tipos genéricos é beneficial, por um lado, e prejudicial por outro. Ao não compilar os tipos genéricos, e efetuar a verificação e conversão estáticamente durante a compilação, a linguagem evita inserir objetos adicionais no código compilado. Caso contrário, existiria uma cópia de ArrayList para Integer, quando o tipo Integer fosse usado, outra para String, e assim por diante. Isso não acontece: apenas uma versão da classe ArrayList é compilada, e todas as suas instâncias armazenam apenas Object. Os objetos são então convertidos de volta para o tipo parametrizado através de type cast, processo que antes fazíamos a mão. Agora a conversão é feita pelo compilador e não resulta em código-fonte entulhado por conversões.

Pelo lado prejudicial, o sistema em tempo de execução não tem como verificar o tipo de genéricos considerados durante a compilação. Por exemplo, qualquer procedimento de Reflection efetuado durante a execução do programa será incapaz de determinar qual o tipo que foi originalmente utilizado, pois toda essa informação foi removida pelo compilador.

Manifest em Scala
Notando a deficiência gerada pela remoção de tipos, a linguagem Scala adotou uma solução não ortodoxa: incluir um “manifesto” ou “lista de bens” atrelada ao programa compilado. Adequadamente chamada de classe Manifest, esta tem como função armazenar dados sobre tipos em tempo de execução. O funcionamento e o uso de Manifest será assunto de um artigo futuro! O importante é saber que a linguagem Scala gera, sim, código adicional para permitir que a reflexão possa ser usada em tempo de execução.

Conclusão
Tipos parametrizados são removidos do programa após o processo de compilação, tornando impossível efetuar Reflection em objetos para descobrir tipos parametrizados. Scala mantém registro dessa informação através de objetos armazenados junto ao código compilado, chamados objetos Manifest. A JVM não mantém dados sobre os tipos parametrizados, os quais são implementados nas linguagens e não na máquina virtual. Para manter essa informação, linguagens baseadas na JVM devem implementar seus próprios recursos.

Standard