Continuando com a missão de tornar o Java mais conciso, os arquitetos responsáveis da Oracle estão explorando pattern matching para se tornar uma nova funcionalidade da linguagem. Brian Goetz, Arquiteto da Linguagem Java na Oracle, e Gavin Bierman, Pesquisador de Linguagens de programação na Oracle, conversaram com o InfoQ sobre a probabilidade de incorporar o pattern matching na linguagem de programação Java.
Motivações
Uma das motivações para essa estudo é melhorar as expressões e estruturação de programação mais comuns no Java. Exemplo:
if (obj instanceof Integer) {
int intValue = ((Integer) obj).intValue();
// use intValue
}
Essas três operações serão processadas:
- Um teste para determinar se obj é do tipo Integer;
- Uma conversão que vai fazer o cast de obj para Integer;
- Uma operação de desestruturação que extrai uma int do Integer.
Agora considere testar também, só que com outros tipos de objetos em uma série de if...else
String formatted = "unknown";
if (obj instanceof Integer) {
int i = (Integer) obj;
formatted = String.format("int %d", i);
}
else if (obj instanceof Byte) {
byte b = (Byte) obj;
formatted = String.format("byte %d", b);
}
else if (obj instanceof Long) {
long l = (Long) obj;
formatted = String.format("long %d", l);
}
else if (obj instanceof Double) {
double d = (Double) obj;
formatted = String.format("double %f", d);
}
else if (obj instanceof String) {
String s = (String) obj;
formatted = String.format("String %s", s);
}
...
Enquanto o código anterior é usado com frequência e facilmente compreendido, ele pode ser tedioso (repetição do código de referência) e fornece uma série de lugares para que os bugs ocorram. O excesso desse tipo de código tende a atrapalhar as regras de negócio do código - por exemplo, fazer o cast dos objetos parece desnecessário e repetitivo já que o instanceof já confirma o tipo da instância do objeto.
Goetz e Bierman explicam os objetivos gerais das melhorias propostas:
Em vez de alcançar soluções ad-hoc, acreditamos que é hora do Java abraçar o pattern matching. Pattern matching é uma técnica utilizada em diversos tipos de linguagens de programação, algumas delas desde a década de 1960, incluindo linguagens orientadas a texto como SNOBOL4 e AWK, linguagens funcionais como Haskell e ML e, mais recentemente, para linguagens orientadas a objetos como Scala e C #.
Um padrão é uma combinação de um predicado que pode ser aplicado a um alvo, juntamente com um conjunto de variáveis de ligação que são extraídas do alvo se o predicado se aplica a ele.
Goetz e Bierman experimentaram vários padrões que incluem novas palavras-chave, como matches e exprswitch
.
Operador Matches
A proposta do operador matches vem para eliminar a necessidade do uso do instanceof. Por exemplo:
if (x matches Integer i) {
// use i here
}
A vinculação da variável i só ocorre se a variável x corresponde a um Integer. Expandindo o exemplo anterior para incluir o outro tipo de dados dentro de uma construção if ... else, é possível eliminar a operação de cast desnecessária.
Aperfeiçoamento no switch
Goetz e Bierman explicam que "a utilização do switch "corresponde" perfeitamente com o pattern matching". Por exemplo:
String formatted;
switch (obj) {
case Integer i: formatted = String.format("int %d", i); break;
case Byte b: formatted = String.format("byte %d", b); break;
case Long l: formatted = String.format("long %d", l); break;
case Double d: formatted = String.format("double %f", d); break;
default: formatted = String.format("String %s", s);
}
...
O código anterior é mais legível e menos complexo. No entanto, Goetz e Bierman apontam uma limitação do uso do switch - "O switch é uma sentença, e portanto seu uso deve ser para sentenças também". Gostaríamos de uma forma de expressão que seja uma generalização do operador condicional, no qual é garantido que ao menos uma das N expressões seja válida.
A solução foi propor uma nova forma de expressão, exprswitch
.
String formatted =
exprswitch (obj) {
case Integer i -> String.format("int %d", i);
case Byte b -> String.format("byte %d", b);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
default -> String.format("String %s", s);
};
...
Em resumo, os patterns propostos por Goetz e Bierman incluem:
- Type-test patterns (liga o alvo a uma variável vinculativa)
- Pattern de Desestruturação (desestrutura o alvo e combina de forma recursiva os componentes com os subpatterns)
- Constant patterns (combina com a igualdade)
- Var patterns (combina com qualquer coisa e atribui ao alvo)
- The _ pattern (combina com qualquer coisa)
Goetz conversou com o InfoQ sobre o pattern matching.
InfoQ: Qual feedback da comunidade que você recebeu desde a publicação do seu artigo?
Goetz: Recebemos feedbacks muito positivos. As pessoas que usaram pattern matching em outras linguagens realmente gostaram e estão felizes em vê-lo chegar no Java. Para as pessoas que ainda não viram isso antes, esperamos que haja um esforço de aprendizado sobre por que pensamos que isso é importante para adicionar à linguagem.
InfoQ: Quanto o design usado em Scala para esse pattern influenciou o projeto até agora? Existe algo que será possível fazer em Scala, mas não será possível usar no Java com o pattern matching?
Goetz: Scala é apenas uma das muitas linguagens que ajudou a formar a nossa idéia sobre a implementação do pattern matching para Java. Adicionar um recurso a uma linguagem raramente é uma simples questão de "portar/migrar" de uma linguagem para outra; Se fizéssemos isso, seria simplesmente replicar algo existente, sem nenhuma criticidade. Provavelmente, acabaremos por não fazer tudo que o pattern matching em Scala faz - e também fazendo algumas coisas que em Scala não faz.
Pensamos que há uma oportunidade de integrar o pattern matching muito mais profundamente no modelo de objeto do que Scala tem. Os padrões de Scala são efetivamente estáticos; eles não podem ser facilmente sobrecarregados ou substituídos. Embora seja muito útil, acreditamos que podemos fazer melhor.
A desconstrução é o sinônimo para construção; Assim como as linguagens OO oferecem opções de como construir objetos (construtores, fábricas, construtores), pensamos que ter as mesmas escolhas para a desconstrução levará a APIs ser mais rica. Embora o pattern matching tenha sido historicamente associada a linguagens funcionais, achamos que ele tem um lugar legítimo em OO também - a qual apenas foi historicamente ignorado.
É fácil ficar entusiasmado com os recursos da linguagem, mas pensamos que as funcionalidades da linguagem devem primeiramente serem facilitadores para melhores bibliotecas - porque há tanta vantagem em ter um ecossistema rico de bibliotecas. E o pattern matching definitivamente permitirá escrever bibliotecas mais simples e seguras.
Como exemplo de uma biblioteca OO que nunca se percebeu que precisava de pattern matching, considere o par de métodos em
java.lang.Class
:public boolean isArray() { ... } public Class getComponentType() { ... }
O segundo método tem uma pré-condição - que o primeiro método retorne verdadeiro. Ter a lógica de uma operação espalhada por vários pontos da API significa complexidade para o gravador da API (quem tem que especificar mais e testar mais) e complexidade para o usuário (quem pode facilmente usar as coisas de forma incorreta). Logicamente, esses dois métodos são um padrão, que misturam o teste de aplicabilidade se "esta classe representa uma classe de array (lista)", com a extração condicional do tipo de componente. Se fosse realmente expressado como tal, seria mais fácil de escrever e impossível de usar incorretamente:
if (aClass matches Class.arrayClass(var componentType)) { ... }
InfoQ: É ou não é um objetivo fornecer tecnologia que permita que o Scala utilize a sua implementação do pattern match sobrepondo a versão do Java em desenvolvimento (de forma semelhante a como vimos os traços do rebase do Scala 2.12 no topo das interfaces)?
Brian Goetz: Assim como com a Lambda, esperamos que, no decorrer da concepção deste recurso, vamos identificar blocos de código sensíveis que possam entrar por baixo da plataforma, assim várias linguagens podem se beneficiar - e oferecer maior interoperabilidade entre múltiplas linguagens.
InfoQ: Na implementação em Scala, quantidades significativas de bytecode adicional são geradas para suportar recursos como a desestruturação de classes. A adição de uma quantidade similar pode fazer com que tenha alguma inconveniência no mecanismo da VM e dos bytecode?
Goetz: O risco é que o compilador está saindo do seu papel normal e atribuindo semântica as classes que geralmente estão sob o controle do desenvolvedor. Embora isso seja conveniente, talvez nem sempre seja o que o usuário deseja - o que muitas vezes leva a solicitações para que marcadores e botões ajustem a geração (por exemplo, comparando arrays com Arrays.equals() em vez de Object.equals()).
InfoQ: A desconstrução será limitada apenas para classes de dados?
Goetz: Planejamos implementar o pattern matching em várias releases, começando com patterns simples, como testes de tipo, então pattern de desestruturação em classes de dados e, eventualmente, patterns arbitrários de desestruturação escritos pelo usuário. Então, como definitivamente não pretendemos limitar a desconstrução às classes de dados, pode haver uma certa janela de tempo até que isso seja implementado e seja verdade.
InfoQ: Pode comentar sobre a relação entre classes de dados e tipos de valores?
Goetz: os dois são quase completamente ortogonais. Os valores são sobre agregados que não têm identidade; ao desautorizar explicitamente a identidade, o tempo de execução pode otimizar o layout na memória, afastar as indireções e os cabeçalhos dos objetos, e mais livremente cachear componentes de valores em pontos de sincronização. As classes de dados são sobre desautorizar relações complexas e indiretas entre uma representação de classes e seu contrato de API; ao fazê-lo, o compilador pode preencher membros da classe comum como construtores, patterns matchers, equals, hashCode e toString. Uma classe pode se adequar para ser uma classe de valor, ou uma classe de dados, ou nenhuma, ou ambas; Todas as combinações fazem sentido.
InfoQ: A implementação em modo privado requer algum suporte do código fonte do compilador.
Goetz: A implementação privada (como final) não só requer suporte do compilador, mas também obter apoio da JVM também - de modo que as restrições de nível de idioma como "X não podem estender Y" são aplicadas pela JVM.
InfoQ: A intenção de privar significa algo como "não poder ter subclasses fora deste módulo"?
Goetz: Há uma série de o que poderia significar privar. A forma mais simples significaria que "só pode ser estendido por outras classes declaradas no mesmo arquivo de origem" - não é apenas uma interpretação comum de privar, mas também é extremamente simples porque as coisas não podem sair da sincronização por meio de compilação separada. Também pode definir a privação como "na mesma embalagem" ou "no mesmo módulo"; pode-se ficar ainda mais complexo e permitir listas de "amigos" ou predicados complexos em tempo de execução. Vamos chegar a uma versão robusta somente no final de sua implementação; O retorno sobre a complexidade além da interpretação mais simples cai muito rápido.
InfoQ: O novo ciclo de novas versões do Java que dura seis meses melhorará a integração do pattern matching na linguagem?
Goetz: Esperamos que sim! Já identificamos "pedaços" sensíveis em que o pattern matching pode ser dividido, de modo que possamos implementar suporte de padrão simples de forma relativamente rápida e continuar sua implementação.
InfoQ: Um protótipo estará disponível para usuários pioneiros em algum momento?
Goetz: Já está! - para os primeiros adeptos que estão dispostos a compilar com o código fonte do JDK. Há uma branch chamada "Amber", com suporte para padrões de teste de tipo em switch e um predicado de "matches".
InfoQ: Qual o futuro para sua pesquisa do pattern matching?
Goetz: ainda estamos explorando como queremos implementar os matchers como membros de classes, e as questões que envolvem a forma como eles desempenham em sobrecarga e herança. Há muito para se descobrir ainda.
Referências
- Towards Pattern Matching in Java by Kerflyn, May 9, 2012
- Pattern Matching in Java by Benji Weber, May 3, 2014
- Pattern Matching in Java with the Visitor Pattern by Kevin Peterson, February 11, 2015
- Adventures in Pattern Matching by Brian Goetz, JVM Language Summit, August 2017
- Moving Java Faster by Mark Reinhold, September 6, 2017
- Java to Move to 6-Monthly Release Cadence by InfoQ, September 6, 2017