Quando escrevi anteriormente que herança não funciona meu objetivo não era realmente provar isso, mas atentar ao fato de que não é possível construir uma linguagem na qual esse recurso se integra completamente com os demais. Nos comentários do artigo se falou de é possível, sob algumas premissas, suportar herança, variância e mutabilidade. Porém ir do object calculi do Luca Cardelli para um linguagem de programação existe o abismo da expressividade.
Vamos aos poucos, qual é, fundamentalmente, o grande problema com herança? Chama-se destructive assigment, ou mutabilidade se preferir. Referencias não operam de maneira transparente com herança, pois atribuição possui variância contrária a leitura e isso leva a quimeras como os arrays do Java. Estes que faltam type soundness, pois uma atribuição pode falhar em runtime. A solução, infelizmente, é bem complexa, pois exige que arrays não tenham relação de herança entre si e isso, por sua vez, exige anotação de variância para criar coisas simples como um método que copia valores entre dois arrays (imagine o System.ArrayCopy do Java ou o Array.Copy do .net).
Anotações de variância, sejam elas opcionais ou inferidas pelo compilador introduzem uma série de problemas, desde declarações de tipo enormes, passando pelas implicações que isso traz ao runtime e, chegando até, as mensagens de erro indecifráveis dos compiladores. Mas, em última instância, a questão maior é que essa decisão sobre o design de uma linguagem é fundamental demais para poder ser introduzido depois, não ser criar quase que uma linguagem nova e, por essa razão apenas, que linguagens como Java ou C# nunca vão ter isso devidamente resolvido.
A contradição que existe entre herança e mutabilidade é, para alguns olhos, parte da beleza da orientação a objetos – não existe solução que seja correta, simples e compreensível ao mesmo tempo. Tuplas, por exemplo, simples em teoria, porém nenhuma linguagem com anotação de tipos realmente as possui pois tem de escolher mutabilidade, subtipagem covariante e tipagem segura estática – uma decisão dificil.
Talvez a questão seja que subtipagem da forma como temos em linguagens como Java não seja de toda útil. Linguagens dinâmicas como Ruby se valem de subsumption polymophism, ou simplesmente duck typing. Propriedade esta que um objeto vale mais pelas mensagens qual ele responde do que pelos seus super tipos. Quando um objeto implementar uma dada interface vale mais pela interface ou pelos métodos que são expostos?
Herança ainda é um problema enorme, porém não sem deixar de ser interessante e intrigante. Mesmo com resultados como o “Typed Object Calculi” do Luca Cardelli, ainda não surgiu uma linguagem que implementa OO nem herança de maneira satisfatória. Mesmo que seja inviável tal linguagem, importante seria termos resultados nesta área.
3 responses so far ↓
1 Proteu Alcebidiano // Jan 1, 2008 at 9:38 pm
Por que é que eu tenho impressão que modelos desenhados sob herança traz para suas sub-classes comportamentos contaminadores?
Passagem de mensagem realmente, parece ser um efeito descontaminador. E vendo herança ser implementada, parece com a idéia de introduzir a construção de produtos não-software para produtos de software.
T+
2 Osvaldo Doederlein // Feb 12, 2008 at 6:28 pm
Sobre sua afirmação que os arrays do Java são incomatíveis com type-soundness. Não são. Há uma pilha de papers demonstrando o type-soundness do Java, ex.: do Drossopoulou. Em geral (IIRC – faz vários anos li essas coisas) são papers que trabalham com um subset do Java, pois como vc deve saber, qq linguagem da complexidade do Java é praticamente intratável para uma prova formal completa. (Até mesmo linguagens “politicamente corretas” como o Haskell forçam pesquisadores a criar subsets para provas formais. Jamais vi uma linguagem realista que escape disso.)
Acho que vc está se confundindo com o probema de falhas em runtime. Nenhum código Java “puro”, sem typecasts (e outras manhas como reflection ou JNI), que compile corretamente, terá erros de tipo em runtime. Inclusive com arrays. O que pode ocorrer por exemplo é um ArrayStoreException em métodos como arraycopy(). Mas isso é uma deficiência dessa API, não da linguagem. É uma API antiga (JDK1.0) que recorre a código nativo para copiar arrays de qq tipo, com otimizações como usar um memcpy() quando possível ex.: arrays de tipos primitivos. Modernamente esse tipo de otimização é besteira, pois o overhead de JNI é relativamente alto e os JITs podem gerar código superior com intrinsics. Por isso temos os novos métodos Arrays.copyOf(), que substituem arraycopy() com mais type-safety (e outras vantagens). Sendo que o copyOf(T[],int) é type-safe para arrays de reference types (desde que o código-cliente seja type-safe segundo os padrões de generics/Java5). Ainda há buracos, logicamente, como a má integração entre arrays e generics, que nos obriga a eventuais @SuppressWarnings para calar a boca do compilador com generics (e implica em perda de garantias type-safe), mas isso não torna a linguagem unsound, basta evitar a mistura de arrays com generics (o que na teoria é fácil, claro que na prática é mais difícil devido à necessidade de usar um mix de APIs que exigem ora arrays, ora collections genéricas).
3 kumpera // Feb 13, 2008 at 11:54 pm
O problema com arrays é que eles são covariantes ao tipo do elemento, o que torna todo store indecidivel em tempo de compilação.
O funcionamento é seguro pois o runtime verifica o tipo armazenado contra o do elemento do array. Porém isso não exige o sistema de tipos de possuir esse furo.
Converter o sistema de tipos do Java para o calculo OO do Cardelli não é uma tarefa trivial exatamente por conta disso.
Indo além do problema com arrays, Java5 é um sistema de tipos fraco da mesma forma que C++ é por possuir type erasure para generics. Característica essa que limitou em muito aquilo que pode ser feito.
Basta comparar aquilo que estão fazendo com C# 2.0. Tuplas eficiente e fortemente tipadas são um exemplo. Em Java generics é usado p/ Collections e todo o resto é perfumaria.
Leave a Comment