Razões para a remoção de tipos de função no Java 8

12

Eu tenho tentado entender por que o JDK 8 Lambda Expert Group (EG) decidiu não incluir um novo tipo de função na linguagem de programação Java.

Revendo a lista de discussão, encontrei um tópico com a discussão sobre remoção de tipos de função .

Muitas das declarações são ambíguas para mim, talvez devido à falta de contexto e, em alguns casos, devido ao meu conhecimento limitado sobre a implementação de sistemas de tipos.

No entanto, existem algumas perguntas que acredito que posso formular com segurança neste site para me ajudar a entender melhor o que elas significam.

Eu sei que poderia fazer as perguntas na lista de discussão, mas o tópico é antigo e todas as decisões já estão tomadas, então é provável que eu seja ignorado, acima de tudo, vendo que esses caras já estão se atrasando em seus planos.

Em sua resposta para apoiar a remoção de tipos de função e endossar o uso de tipos SAM, Brian Goetz diz:

Sem reificação. Havia um longo tópico sobre o quão útil seria para os tipos de funções serem reificados. Sem reificação, os tipos de função são prejudicados.

Não consegui encontrar o fio que ele menciona. Agora, eu posso entender que a introdução de um tipo de função estrutural pode implicar certas complicações no sistema de tipos principalmente Java, o que não consigo entender é como os tipos SAM parametrizados são diferentes em termos de reificação.

Os dois não estão sujeitos aos mesmos problemas de reificação? Alguém entende como os tipos de função são diferentes dos tipos parametrizados de SAM em termos de reificação?

Em outro comentário, Goetz diz:

Existem duas abordagens básicas para a digitação: nominal e estrutural. A identidade de um nominal é baseada em seu nome; a identidade de um tipo estrutural é baseada no que é composto (como "tupla de int, int" ou "função de int para flutuar".) A maioria das línguas escolhe principalmente nominal ou principalmente estrutural; não há muitos idiomas que combinam com sucesso a tipagem nominal e estrutural, exceto "nas bordas". Java é quase inteiramente nominal (com algumas exceções: matrizes são do tipo estrutural, mas na parte inferior sempre existe um tipo de elemento nominal; os genéricos também têm uma mistura de nominal e estrutural, e isso é de fato parte da fonte de muitos reclamações das pessoas sobre genéricos.) Enxertando um sistema de tipo estrutural (tipos de função) no Java ' s sistema do tipo nominal significa nova complexidade e casos extremos. O benefício dos tipos de função vale isso?

Aqueles de vocês com experiência na implementação de sistemas de tipos. Você conhece algum exemplo dessas complexidades ou casos extremos que ele menciona aqui?

Honestamente, fico confuso com essas alegações quando considero que uma linguagem de programação como Scala, inteiramente baseada na JVM, oferece suporte a tipos estruturais, como funções e tuplas, mesmo com os problemas de reificação da plataforma subjacente.

Não me interpretem mal, não estou dizendo que um tipo de função deve ser melhor que os tipos SAM. Eu só quero entender por que eles tomaram essa decisão.

edalorzo
fonte
4
"Enxertar um sistema de tipos estruturais (tipos de funções) no sistema de tipos nominais de Java significa nova complexidade" - isso provavelmente foi concebido como uma complexidade para os usuários que precisam aprender mais para usar a linguagem . Algo como Java simples e fácil de usar se torna complexo e difícil de aprender C ++ , coisas assim. Membros da lista de discussão poderia provavelmente alusão às críticas de antes "grande onda de melhorias", um exemplo proeminente sendo Java 5 suga artigo de Clinton Começar de MyBatis
mosquito
3
"Java simples e fácil de usar torna-se complexo e difícil de aprender C ++": Infelizmente, um problema com as principais linguagens de programação é que elas precisam manter a compatibilidade com versões anteriores e, portanto, tendem a se tornar muito complexas. Portanto, IMHO (com muitos anos de experiência com C ++ e Java), há algo verdadeiro nessa afirmação. Costumo ter a sensação de que o Java deve ser congelado e uma nova linguagem deve ser desenvolvida. Mas a Oracle não pode correr esse risco.
Giorgio
4
@edalorzo: Como Clojure, Groovy, Scala e outras linguagens mostraram, é possível (1) definir uma nova linguagem (2) usar toda a riqueza de bibliotecas e ferramentas já escritas em Java sem quebrar a compatibilidade com versões anteriores (3) Os programadores de Java têm a liberdade de escolher se desejam permanecer no Java, alternar para outro idioma ou usar os dois. Na OMI, expandir indefinidamente um idioma existente é uma estratégia para manter os clientes, da mesma forma que as empresas de telefonia móvel precisam criar novos modelos o tempo todo.
Giorgio
2
Como eu entendi no SAMbdas em Java , um dos motivos é que seria problemático manter algumas bibliotecas (coleções) compatíveis com versões anteriores.
Petr Pudlák
2
Post relacionado no SO, com links para este outro post de Goetz .
assylias

Respostas:

6

A abordagem SAM é realmente um pouco semelhante ao que Scala (e C ++ 11) fazem com funções anônimas (criadas com o =>operador do Scala ou com a []()sintaxe do C ++ 11 (lambda)).

A primeira pergunta a ser respondida no lado Java é se o tipo de retorno de uma instrução lambda é um novo tipo primitivo, como intou byte, ou um tipo de objeto de algum tipo. Em Scala, não existem tipos primitivos - mesmo um número inteiro é um objeto de classe Int- e as funções não são diferentes, sendo objetos de classe Function1, Function2etc., dependendo do número de argumentos que a função recebe.

C ++ 11, ruby ​​e python também têm expressões lambda que retornam um objeto que pode ser chamado de maneira explícita ou implícita. O objeto retornado possui algum método padrão (como o #callque pode ser usado para chamá-lo como uma função. C ++ 11, por exemplo, usa um std::functiontipo que sobrecarrega operator()para que as chamadas do método de chamada do objeto pareçam chamadas de função, textualmente. :-)

Onde a nova proposta para Java fica confusa é no uso de tipagem estrutural para permitir atribuir esse método a outro objeto, como um Comparatorque possua um único método principal com um nome diferente . Enquanto isso é conceitualmente icky em alguns aspectos, ele faz significa que o objeto resultante pode ser passado para as funções existentes que levam um objeto para representar um callback, um comparador, e assim por diante, e esperar para ser capaz de chamar um único bem definida método como #compareou #callback. O truque do C ++ de substituir com operator()perfeição esse problema mesmo antes do C ++ 11, já que todas essas opções de retorno de chamada eram passíveis de chamar da mesma maneira e, portanto, o STL não exigia nenhum ajuste para permitir que lambdas do C ++ 11 fossem usadas comsorte assim por diante. O Java, sem ter usado uma nomeação padrão para esses objetos no passado (talvez porque nenhum truque como a sobrecarga de operadores tenha tornado óbvia uma única abordagem), não tem tanta sorte, portanto esse hack impede que eles precisem alterar muitas APIs existentes. .

jimwise
fonte
1
Ironicamente, o problema de ter uma infinidade de tipos diferentes e incompatíveis, todos representando algum tipo de "coisa parecida com uma função", poderia ter sido evitado com a adição de tipos de função padrão à biblioteca desde o início, independentemente da sintaxe literal real para construí-los. Se houvesse um java.util.Function2<T1, T2, R>no Java 1.0, não haveria Comparatorinterface, em vez disso, todos esses métodos receberiam um Function2<T1, T2, int>. (Bem, não teria ser uma faixa inteira de interfaces, como Function2TT_int<T1, T2>causa de primitivas, mas você começa o meu ponto.)
Jörg W Mittag