Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

A primeira vez que acessar o developerWorks, um perfil será criado para você. Informações do seu perfil (tais como: nome, país / região, e empresa) estarão disponíveis ao público, que poderá acompanhar qualquer conteúdo que você publicar. Seu perfil no developerWorks pode ser atualizado a qualquer momento.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Pensamento Funcional: Recursos funcionais no Groovy, Parte 2

Metaprogramação + Java funcional

Neal Ford, Application Architect, ThoughtWorks
Neal Ford
Neal Ford é arquiteto de software e Meme Wrangler na ThoughtWorks, uma consultoria global de TI. Ele também projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD e é autor ou editor de livros que abordam várias tecnologias, incluindo o mais recente The Productive Programmer. Sua especialidade é o projeto e a criação de aplicativos corporativos de grande porte. Também é um orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu website Website.

Resumo:  Com o Groovy, a metaprogramação e a programação funcional formam uma combinação poderosa. Veja como a metaprogramação permite que você adicione métodos ao tipo de dados de número inteiro que aproveita as vantagens dos recursos funcionais integrados do Groovy. E aprenda como usar a metaprogramação para incorporar perfeitamente o conjunto avançado de recursos funcionais da estrutura do Functional Java™ ao Groovy.

Visualizar mais conteúdo nesta série

Data:  18/Jan/2012
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  1908 visualizações
Comentários:  


Sobre esta série

Esta série visa reorientar sua perspectiva para uma mentalidade funcional, ajudando a encarar problemas comuns de maneiras novas e encontrar maneiras de melhorar sua codificação diária. Ela explora os conceitos de programação funcional, estruturas que permitem programação funcional dentro da linguagem Java, linguagens de programação funcional executadas em JVM e algumas indicações de tendência futura do design da linguagem. A série é voltada para desenvolvedores que conhecem Java e como suas abstrações funcionam, mas têm pouca ou nenhuma experiência na utilização de uma linguagem funcional.

No último artigo, mostrei alguns dos recursos funcionais originais do Groovy e como usar as primitivas do Groovy para desenvolver uma lista infinita. Neste artigo, continuo minha exploração da intersecção da programação funcional e do Groovy.

Groovy é uma linguagem de diversos paradigmas: ela oferece suporte à orientação de objeto, metaprogramação e estilos de programação funcional, que são em sua maioria ortogonais com relação ao outro (consulte a barra lateral Ortogonalidade ). A metaprogramação permite que você adicione recursos a uma linguagem e suas bibliotecas principais. Ao combinar a metaprogramação com a programação funcional, é possível tornar seu próprio código mais funcional ou aumentar bibliotecas funcionais de terceiros para que funcionem melhor no Groovy. Primeiro, mostrarei como funciona o ExpandoMetaClass do Groovy a fim de aumentar as classes e, depois, como usar esse mecanismo para integrar a biblioteca Functional Java (consulte Recursos) ao Groovy.

Classes abertas via ExpandoMetaClass

Ortogonalidade

A definição de ortogonal engloba diversas disciplinas, incluindo matemática e ciência da computação. Em matemática, dois vetores que são perpendiculares são ortogonais, ou seja, eles nunca realizam uma intersecção. Em ciência da computação, componentes ortogonais não têm qualquer efeito (ou efeito colateral) um sobre o outro. Por exemplo, a programação funcional e a metaprogramação são ortogonais no Groovy, pois não se interferem: o uso da metaprogramação não o impede de usar desenvolvimentos funcionais e vice-versa. O fato de serem ortogonais não significa que não podem trabalhar juntas, simplesmente porque elas não interferem uma na outra.

Um dos recursos mais eficientes do Groovy é a classe aberta, a capacidade de reabrir uma classe existente a fim de aumentar ou remover sua funcionalidade. Isso é diferente de definir como subclasse, pelo fato de um novo tipo ser obtido a partir de um existente. As classes abertas permitem que você reabra uma classe como uma cadeia de caracteres e adicione novos métodos a ela. As bibliotecas de teste usam bastante esse recurso para aumentar o Objeto com métodos de verificação, de modo que todas as classes em um aplicativo tenham os métodos de verificação.

O Groovy tem duas técnicas de classe aberta: categorias e ExpandoMetaClass (consulte Recursos). As duas funcionarão para este exemplo; escolhi ExpandoMetaClass , pois é sintaticamente mais simples.

Se você está acompanhando esta série, estará familiarizado com meu exemplo recorrente de classificação de número. O Classifier completo no Groovy, exibido na Listagem 1, use os desenvolvimentos funcionais do próprio Groovy:


Listagem 1. Classifier completo no Groovy

class Classifier {
  def static isFactor(number, potential) {
    number % potential == 0;
  }

  def static factorsOf(number) {
    (1..number).findAll { i -> isFactor(number, i) }
  }

  def static sumOfFactors(number) {
    factorsOf(number).inject(0, {i, j -> i + j})
  }

  def static isPerfect(number) {
    sumOfFactors(number) == 2 * number
  }

  def static isAbundant(number) {
    sumOfFactors(number) > 2 * number
  }

  def static isDeficient(number) {
    sumOfFactors(number) < 2 * number
  }

  static def nextPerfectNumberFrom(n) {
    while (!isPerfect(++n));
    n
  }
}

Se você tiver alguma dúvida sobre como os métodos são implementados nesta versão, consulte os artigos anteriores (em particular, "Acoplamento e composição, Parte 2" e "Recursos funcionais no Groovy, Parte 1"). Para usar os métodos desta classe, posso chamar os métodos da maneira funcional "normal": Classifier.isPerfect(7). No entanto, com a metaprogramação, posso "conectar" esses métodos diretamente na classe Integer , permitindo que eu "pergunte" a um número em qual categoria ele está.

Para adicionar esses métodos à classe Integer , eu acesso a propriedade metaClass da classe — predefinida pelo Groovy para cada classe — como mostra a Listagem 2:


Listagem 2. Adicionando classificação a Integer

Integer.metaClass.isPerfect = {->
  Classifier.isPerfect(delegate)
}

Integer.metaClass.isAbundant = {->
  Classifier.isAbundant(delegate)
}

Integer.metaClass.isDeficient = {->
  Classifier.isDeficient(delegate)
}

Inicializando os métodos de metaprogramação

É preciso adicionar métodos de metaprogramação antes da primeira tentativa de invocá-los. O local mais seguro para inicializá-los é no inicializador estático para a classe que os usa (pois é garantido que executará antes de outros inicializadores para a classe), mas isso adiciona complexidade quando diversas classes precisam de métodos aumentados. Geralmente, os aplicativos que usam muita metaprogramação acabam com uma classe de autoinicialização a fim de garantir que os inicializadores ocorram no tempo apropriado.

Na Listagem 2, adicionei três métodos de Classifier ao Integer. Agora, todos os números inteiros no Groovy têm esses métodos. (O Groovy não tem noção de tipos de dados primitivos; até mesmos constantes no Groovy usam Integer como o tipo de dados subjacente.) No bloco de código que define cada método, tenho acesso ao parâmetro delegate predefinido, que representa o valor do objeto que está invocando o método na classe.

Depois de inicializar meus métodos de metaprogramação (consulte a barra lateral Inicializando os métodos de metaprogramação ), posso "perguntar" aos números sobre as categorias, como mostra a Listagem 3:


Listagem 3. Usando a metaprogramação para classificar os números

@Test
void metaclass_classifiers() {
  def num = 28
  assertTrue num.isPerfect()
  assertTrue 7.isDeficient()
  assertTrue 6.isPerfect()
  assertTrue 12.isAbundant()
}

A Listagem 3 ilustra os métodos recém-adicionados trabalhando em variáveis e constantes. Agora, seria trivial adicionar um método ao Integer que retorna a classificação de um número específico, talvez como uma enumeração.

A adição de novos métodos às classes existentes não é por si só particularmente "funcional", mesmo se o código que eles chamam for altamente funcional. No entanto, a capacidade de adicionar métodos de forma perfeita facilita a incorporação de bibliotecas de terceiros — como a biblioteca Functional Java — que adiciona recursos funcionais consideráveis. Implementei o classificador de número usando a biblioteca Functional Java no segundo artigo e o usarei aqui para criar um fluxo infinito de números perfeitos.


Mapeando os tipos de dados com a metaprogramação

O Groovy é essencialmente um dialeto de Java, portanto, usar bibliotecas de terceiros como a Functional Java é trivial. No entanto, posso integrar ainda mais essas bibliotecas ao Groovy realizando algum mapeamento de metaprogramação entre os tipos de dados para tornar os pontos menos visíveis. O Groovy tem um tipo de encerramento nativo (usando a classe Closure ). A Functional Java ainda não tem o luxo dos encerramentos (ela depende da sintaxe de Java 5), forçando os autores a usar genéricos e uma classe F geral que contém um método f() . Usando ExpandoMetaClass do o Groovy, posso resolver as diferenças entre o método/tipo de encerramento criando métodos de mapeamento entre os dois.

A classe que eu desejo aumentar é a classe Stream da Functional Java, que fornece uma abstração para listas infinitas. Desejo poder passar encerramentos do Groovy no lugar de instâncias F de Functional Java, portanto, adiciono métodos sobrecarregados à classe Stream a fim de mapear os encerramentos no método f () de F , como mostra a Listagem 4:


Listagem 4. Mapeando tipos de dados usando ExpandoMetaClass

Stream.metaClass.filter = { c -> delegate.filter(c as fj.F) }
//    Stream.metaClass.filter = { Closure c -> delegate.filter(c as fj.F) }
Stream.metaClass.getAt = { n -> delegate.index(n) }
Stream.metaClass.getAt = { Range r -> r.collect { delegate.index(it) } }

A primeira linha cria um método filter() no Stream que aceita um encerramento (o parâmetro c do bloco de código). A segunda linha (comentada) é igual à primeira, mas com o acréscimo da declaração de tipo para o Closure; isso não afeta o modo como o Groovy executa o código, mas pode ser mais desejado como documentação. O corpo do bloco de códigos chama o método pré-existente filter()de Stream , mapeando o encerramento do Groovy para a classe fj.F de Functional Java. Eu uso um operador semimágico do Groovy, as , para realizar o mapeamento.

O operador as do Groovy coage os encerramentos em definições de interface, permitindo que os métodos de encerramento mapeiem para os métodos exigidos pela interface. Considere o código na Listagem 5:


Listagem 5. Usando as para criar um agente iterativo leve

def h = [hasNext : { println "hasNext called"; return true},
         next : {println "next called"}] as Iterator

h.hasNext()
h.next()
println "h instanceof Iterator? " + (h instanceof Iterator)

No exemplo da Listagem 5, eu criei um hash com dois pares nome-valor. Cada um dos nomes é uma cadeia de caractere (o Groovy não exige que as chaves hash sejam delimitadas com aspas duplas, pois elas são cadeias de caractere por padrão) e os valores são blocos de código. O operador as mapeia esse hash até a interface Iterator , que exige os métodos hasNext() e next() . Depois de realizar o mapeamento, posso tratar o hash como um agente iterativo, a última linha da listagem mostra true. Em casos em que tenho uma interface de método único ou quando desejo que todos os métodos na interface mapeiem para um único encerramento, posso dispensar o hash e usar as diretamente para mapear um encerramento em uma função. Observando novamente a primeira linha da Listagem 4, eu mapeio o encerramento passado para a classe F de método único. Na Listagem 4, preciso mapear ambos os métodos getAt (um que aceite um número e outro que aceite um Range), pois filter precisa desses métodos para operar.

Usando esse Stream recém-aumentado, posso brincar com uma sequência infinita, como mostra a Listagem 6:


Listagem 6. Usando fluxos infinitos de Functional Java no Groovy

@Test
void adding_methods_to_fj_classes() {

  def evens = Stream.range(0).filter { it % 2 == 0 }
  assertTrue(evens.take(5).asList() == [0, 2, 4, 6, 8])
  assertTrue(evens[3..6] == [6, 8, 10, 12])
}

Na Listagem 6, eu crio uma lista infinita de números inteiros pares, começando com 0, filtrando-os com um bloco de encerramento. Não é possível obter toda a sequência infinita de uma vez, portanto, você precisa take() o máximo de elementos que conseguir. O restante da Listagem 6 mostra asserções de teste que demonstram como o fluxo funciona.


Fluxos infinitos no Groovy

No último artigo, mostrei como implementar uma lista infinita no Groovy. Em vez de criá-la manualmente, por que não depender de uma sequência infinita da Functional Java?

Para criar um Stream infinito de números perfeitos, preciso de dois mapeamentos adicionais do método Stream para entender os encerramentos do Groovy, como mostra a Listagem 7:


Listagem 7. Dois mapeamentos de método adicionais para um fluxo de número perfeito

Stream.metaClass.asList = { delegate.toCollection().asList() }
Stream.metaClass.static.cons = { head, closure -> delegate.cons(head, closure as fj.P1) }
// Stream.metaClass.static.cons =
//  { head, Closure c -> delegate.cons(head, ['_1':c] as fj.P1)}

Na Listagem 7, criei um método de conversão asList() para facilitar a conversão de um fluxo de Functional Java para uma lista. O outro método que implemento é um cons()sobrecarregado, que é o método em Stream que desenvolve uma nova lista. Ao criar uma lista infinita, a estrutura de dados normalmente contém um primeiro elemento e um bloco de encerramento como a cauda da lista, que gera o próximo elemento, quando invocado. Para meu fluxo do Groovy de números perfeitos, eu preciso de Functional Java para entender que cons() pode aceitar um encerramento do Groovy.

Se eu usar as para mapear um único encerramento até uma interface que possui diversos métodos, esse encerramento será executado para qualquer método que eu chame na interface. Esse estilo de mapeamento simples funciona na maioria dos casos para classes de Functional Java. No entanto, alguns métodos exigem um método fj.P1 em vez de um método fj.F . Em alguns desses casos, ainda posso conseguir um mapeamento simples, pois os métodos de recebimento de dados não dependem de qualquer um dos outros métodos de P1. Em casos nos quais é necessário ter mais precisão, talvez eu tenha que usar o mapeamento mais complexo exibido na linha comentada da Listagem 7, que precisa criar um hash com o método _1() mapeado para o encerramento. Embora esse método pareça estranho, é um método padrão na classe fj.P1 que retorna o primeiro elemento.

Assim que eu tiver meus métodos mapeados metaprogramaticamente no Stream, poderei usar o Classifier da Listagem 1 para criar um fluxo infinito de números perfeitos, como mostra a Listagem 8:


Listagem 8. Fluxo infinito de números perfeitos usando Functional Java e Groovy

import static fj.data.Stream.cons
import static com.nealford.ft.metafunctionaljava.Classifier.nextPerfectNumberFrom

def perfectNumbers(num) {
  cons(nextPerfectNumberFrom(num), { perfectNumbers(nextPerfectNumberFrom(num))})
}

@Test
void infinite_stream_of_perfect_nums_using_functional_java() {
  assertEquals([6, 28, 496], perfectNumbers(1).take(3).asList())
}

Eu uso importações estáticas para cons() do Functional Java e para meu próprio método nextPerfectNumberFrom() do Classifier para tornar o código menos detalhado. O método perfectNumbers() retorna uma sequência infinita de números perfeitos alocando (sim, alocar é um verbo) o primeiro número perfeito após o número inicial como o primeiro elemento e adicionando um bloco de encerramento como o segundo elemento. O bloco de encerramento retorna a sequência infinita com o próximo número como a cabeça e o encerramento para calcular o outro como a cauda. No teste, gerei um fluxo de números perfeitos começando com 1, obtendo os três próximos números e assegurando que eles correspondem à lista.


Conclusão

Quando os desenvolvedores pensam em metaprogramação, eles normalmente pensam somente em seu próprio código, não em aumentar o de outra pessoa. O Groovy permite que eu adicione novos métodos não apenas a classes integradas como Integer, mas também a bibliotecas de terceiros como Functional Java. A combinação de metaprogramação e programação funcional gera um grande poder com muito pouco código, criando um link perfeito.

Embora eu chame as classes de Functional Java diretamente do Groovy, muitos dos blocos de construção da biblioteca são desajeitados comparados com encerramentos reais. Ao usar a metaprogramação, posso mapear os métodos de Functional Java a fim de permitir que eles entendam as estruturas de dados convenientes do Groovy, conseguindo o melhor de ambos os mundos. Até que o Java defina um tipo de encerramento nativo, os desenvolvedores precisam realizar com frequência esses mapeamentos poliglotas entre os tipos de linguagem: um encerramento do Groovy e um encerramento Scala não são a mesma coisa no nível de bytecode. Ter um padrão em Java colocará essas conversas no tempo de execução e eliminará a necessidade de mapeamentos como aqueles que mostrei aqui. Até que isso aconteça, no entanto, esse recurso permite um código simples, mas ainda assim eficiente.

No próximo artigo, falarei sobre algumas otimizações permitidas pela programação funcional ao seu tempo de execução a fim de criar e mostrar exemplos no Groovy de memorização.


Recursos

Aprender

Obter produtos e tecnologias

  • Functional Java: faça o download da estrutura de Functional Java.

  • Avalie os produtos IBM da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox aprendendo a implementar Arquitetura Orientada a Serviços de modo eficiente.

Discutir

  • Participe da comunidade do developerWorks. Entre em contato com outros usuários do developerWorks e explore os blogs, fóruns, grupos e wikis voltados para desenvolvedores.

Sobre o autor

Neal Ford

Neal Ford é arquiteto de software e Meme Wrangler na ThoughtWorks, uma consultoria global de TI. Ele também projeta e desenvolve aplicativos, materiais de instrução, artigos para revistas, treinamentos e apresentações em vídeo/DVD e é autor ou editor de livros que abordam várias tecnologias, incluindo o mais recente The Productive Programmer. Sua especialidade é o projeto e a criação de aplicativos corporativos de grande porte. Também é um orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu website Website.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java
ArticleID=785376
ArticleTitle=Pensamento Funcional: Recursos funcionais no Groovy, Parte 2
publish-date=01182012