Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições 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.

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 1

Tesouros ocultos no Groovy

Neal Ford, Software Architect / Meme Wrangler, ThoughtWorks Inc.
Neal Ford
Neal Ford é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. 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 uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

Resumo:  Com o tempo, as linguagens e tempos de execução passaram a lidar cada vez mais com detalhes rotineiros. As linguagens funcionais exemplificam essa tendência, mas as linguagens dinâmicas modernas também incorporaram muitos recursos funcionais para facilitar as vidas dos desenvolvedores. Este artigo investiga alguns dos recursos funcionais que já existem no Groovy, mostrando como a recursão oculta o estado e como desenvolver listas lentas.

Visualizar mais conteúdo nesta série

Data:  09/Dez/2011
Nível:  Intermediário Também disponível em :   Inglês
Atividade:  599 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.

Confissão: nunca mais quero trabalhar em uma linguagem que não seja coletora de lixo. Já trabalhei muito em linguagens como C++ e não quero ceder às conveniências das linguagens modernas. Essa é a história de como o desenvolvimento de software progride. Desenvolvemos camadas de abstração para lidar (e ocultar) detalhes rotineiros. À medida que a capacidade dos computadores cresceu, transferimos mais tarefas para as linguagens e tempos de execução. Recentemente, há uma década mais precisamente, os desenvolvedores evitavam linguagens interpretadas por serem muito lentas para aplicativos de produção, mas hoje elas são comuns. Há uma década, muitos dos recursos das linguagens funcionais eram extremamente lentos, mas fazem todo sentido agora, pois otimizam o tempo e o esforço do desenvolvedor.

Muitos dos recursos abordados nesta série de artigos mostram como as linguagens e estruturas funcionais lidam com detalhes rotineiros. No entanto, você não precisa recorrer a uma linguagem funcional para se beneficiar dos desenvolvimentos funcionais. Neste artigo e no próximo, mostrarei como algumas programações funcionais já fazem parte do Groovy.

Listas funcionais do Groovy

O Groovy aumenta consideravelmente as bibliotecas de coleção Java, incluindo a adição de desenvolvimentos funcionais. A primeira funcionalidade do Groovy é fornecer uma perspectiva diferente sobre as listas, o que parece comum a princípio, mas oferece alguns benefícios interessantes.

Vendo as listas de forma diferente

Se você tem experiência com linguagens C ou parecidas com C (incluindo Java), provavelmente, você conceitualiza as listas como coleções indexadas. Essa perspectiva facilita a iteração sobre uma coleção, mesmo quando você não usa explicitamente o índice, como mostra o código do Groovy na Listagem 1:


Listagem 1. Lista transversal usando índices (ocultos)

def perfectNumbers = [6, 28, 496, 8128]

def iterateList(listOfNums) {
  listOfNums.each { n ->
    println "${n}"
  }
}

iterateList(perfectNumbers)

O Groovy também inclui um iterador eachWithIndex() que fornece o índice como um parâmetro para o bloco de códigos para casos nos quais é necessário o acesso explícito. Apesar de não usar um índice no método iteraterList() na Listagem 1, ainda penso nele como uma coleção organizada de slots, como mostra a Figura 1:


Figura 1. Listas como slots indexados

Muitas linguagens funcionais têm uma perspectiva um pouco diferente sobre as listas e, felizmente, o Groovy compartilha essa perspectiva. Em vez de pensar em uma lista como slots indexados, pense nela como uma combinação do primeiro elemento na lista (o início) com o restante da lista (o fim), como mostra a Figura 2:


Figura 2. Uma lista com início e fim

Pensar em uma lista com início e fim permite que eu itere usando a recursão, como mostra a Listagem 2:


Listagem 2. Lista transversal usando recursão

def recurseList(listOfNums) {
  if (listOfNums.size == 0) return;
    println "${listOfNums.head()}"
    recurseList(listOfNums.tail())
}

recurseList(perfectNumbers)

No método recurseList() na Listagem 2, primeiramente, verifiquei se a lista passada como o parâmetro não contém elementos. Se esse for o caso, poderei retornar. Caso contrário, imprimo o primeiro elemento na lista, disponível por meio do método head() do Groovy e chamo o método recurseList() de forma recursiva no restante da lista.

A recursão tem limites técnicos integrados à plataforma (consulte Recursos), portanto, isso não é uma solução geral. Mas deve funcionar para listas que contêm um pequeno número de itens. Estou mais interessado em investigar o impacto sobre a estrutura do código, antecipando o dia em que os limites serão facilitados ou desaparecerão. Graças a algumas falhas, talvez o benefício da versão recursiva não seja imediatamente óbvio. Para torná-lo mais óbvio, considere o problema de filtragem de uma lista. Na Listagem 3, mostro um exemplo de um método de filtragem que aceita uma lista e um predicado (um teste booleano) a fim de determinar se o item pertence à lista:


Listagem 3. Filtragem imperativa com o Groovy

def filter(list, p) {
  def new_list = []
  list.each { i ->
    if (p(i))
      new_list << i
  }
  new_list
}

modBy2 = { n -> n % 2 == 0}

l = filter(1..20, modBy2)

O código na Listagem 3 é bastante direto: crio uma variável portadora para os elementos que desejo manter, itero sobre a lista, verifico cada elemento com o predicado de inclusão e retorno a lista de itens filtrados. Quando chamo filter(), forneço um bloco de códigos que especifica os critérios de filtragem.

Considere uma implementação recursiva do método de filtragem da Listagem 3, mostrado na Listagem 4:


Listagem 4. Filtragem recursiva com o Groovy

def filter(list, p) {
  if (list.size() == 0) return list
  if (p(list.head()))
    [] + list.head() + filter(list.tail(), p)
  else
    filter(list.tail(), p)
}

l = filter(1..20, {n-> n % 2 == 0})

No método filter() , na Listagem 4, primeiro verifico o tamanho da lista passada e, se não houver elementos, a retorno. Caso contrário, verifico o início da lista com base em meu predicado de filtragem. Se passar, eu adiciono à lista (com uma lista vazia inicial para ter certeza de que sempre retorno o tipo correto), caso contrário, eu filtro o fim.

A diferença entre a Listagem 3 e a Listagem 4 destaca uma questão importante: quem se importa com o estado? Na versão imperativa, eu me importo. Eu preciso criar uma nova variável chamada new_list, e preciso adicionar coisas a ela e retorná-la após a conclusão. Na versão recursiva, a linguagem gerencia o valor de retorno, desenvolvendo-o sobre a pilha como o retorno recursivo de cada invocação de método. Perceba que cada rota de saída do método filter() na Listagem 4 é uma chamada de retorno, que desenvolve o valor intermediário sobre a pilha.

Embora não seja uma melhoria tão importante quanto a coleta de lixo, ilustra uma tendência importante em linguagens de programação: a transferência de partes em movimentação. Se eu nunca puder acessar os resultados intermediários da lista, não poderei apresentar bugs na maneira em que interajo com ela.

Essa mudança de perspectiva com relação às listas permite que você explore outros aspectos, como o tamanho e o escopo da lista.

Listas lentas no Groovy

Um dos recursos comuns das linguagens funcionais é a lista lenta: uma lista cujo conteúdo é gerado somente quando você precisa dele. Essas listas permitem que você adie a inicialização de recursos dispendiosos até que eles sejam absolutamente necessários. Elas também permitem a criação de sequências infinitas: listas sem limite superior. Se não for necessário saber logo no início o tamanho da lista, é possível deixá-la com o tamanho que desejar.

Primeiramente, mostrarei um exemplo de como usar uma lista lenta no Groovy na Listagem 5 e depois sua implementação:


Listagem 5. Usando listas lentas no Groovy

def prepend(val, closure) { new LazyList(val, closure) }

def integers(n) { prepend(n, { integers(n + 1) }) }

@Test
public void lazy_list_acts_like_a_list() {
  def naturalNumbers = integers(1)
  assertEquals('1 2 3 4 5 6 7 8 9 10',
      naturalNumbers.getHead(10).join(' '))
  def evenNumbers = naturalNumbers.filter { it % 2 == 0 }
  assertEquals('2 4 6 8 10 12 14 16 18 20',
      evenNumbers.getHead(10).join(' '))
}

O primeiro método na Listagem 5, prepend(), cria um novo LazyList, permitindo que você anexe previamente os valores. O próximo método, integers(), retorna uma lista de números inteiros usando o método prepend() . Os dois parâmetros enviados ao método prepend() são: o valor inicial da lista e um bloco de código que inclui um código para gerar o próximo valor. O método integers() age como uma fábrica que retorna a lista lenta de números inteiros com um valor na frente e uma forma de calcular valores adicionais na parte de trás.

Para recuperar os valores da lista, eu chamo o método getHead() , o que retorna o número do argumento de valores da parte superior da lista. Na Listagem 5, naturalNumbers é uma sequência lenta de todos os números inteiros. Para obter alguns deles, eu chamo o método getHead() , especificando quantos números inteiros desejo. Como indica a asserção, eu recebo uma lista dos 10 primeiros números naturais. Usando o método filter() , eu recupero uma lista lenta de números pares e chamo o método getHead() para obter os 10 primeiros números pares.

A implementação da LazyList é exibida na Listagem 6:


Listagem 6. Implementação da LazyList

class LazyList {
  private head, tail

  LazyList(head, tail) {
    this.head = head;
    this.tail = tail
  }

  def LazyList getTail() { tail ? tail() : null }

  def List getHead(n) {
    def valuesFromHead = [];
    def current = this
    n.times {
      valuesFromHead << current.head
      current = current.tail
    }
    valuesFromHead
  }

  def LazyList filter(Closure p) {
    if (p(head))
      p.owner.prepend(head, { getTail().filter(p) })
    else
      getTail().filter(p)
  }
}


Uma lista lenta possui um início e um fim que são especificados no construtor. O método getTail() garante que o fim não seja nulo e a executa. O método getHead() reúne os elementos que desejo retornar, um por vez, obtendo o elemento existente do início da lista e pedindo ao fim para gerar um novo valor. A chamada para n.times {} realiza essa operação para o número de elementos solicitados e o método retorna os valores colhidos.

O método filter() , na Listagem 5 usa a mesma abordagem recursiva que a Listagem 4 , mas a implementa como parte da lista e não como uma função autônoma.

As listas lentas estão presentes em Java (consulte Recursos), mas são muito mais fáceis de implementar em linguagens com recursos funcionais. As listas lentas funcionam muito bem em situações nas quais a geração de recursos é dispendiosa, como na obtenção de listas de números perfeitos.

Lista lenta de números perfeitos

Se você acompanha esta série de artigos, conhece meu código teste favorito, localizar números perfeitos (consulte Pensando funcionalmente, Parte 1)"). Até agora, uma das desvantagens de todas as implementações é a necessidade de especificar o número para classificação. Em vez disso, desejo uma versão que retorna uma lista lenta de números perfeitos. Para isso, escrevi um localizador de números perfeitos altamente funcional e muito compacto que suporta listas lentas. Veja na Listagem 7:


Listagem 7. Versão reduzida do classificador de número, incluindo nextPerfectNumberFrom() method

class NumberClassifier {
  static def factorsOf(number) {
      (1..number).findAll { i -> number % i == 0 }
  }

  static def isPerfect(number) {
      factorsOf(number).inject(0, {i, j -> i + j}) == 2 * number
  }

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

Se o código nos métodos factorsOf() e isPerfect() parecer difícil, você poderá ver a derivação desses métodos no último artigo. O novo método, nextPerfectNumber(), usa o método isPerfect() para encontrar o próximo número perfeito além do número passado como parâmetro. Essa chamada de método irá demorar muito para executar valores pequenos (especialmente devido à falta de otimização do código), simplesmente não há tantos números perfeitos assim.

Usando essa nova versão do NumberClassifier, posso criar uma lista lenta de números perfeitos, como mostra a Listagem 8:


Listagem 8. Lista inicializada lentamente de números perfeitos

def perfectNumbers(n) {
  prepend(n,
    { perfectNumbers(nextPerfectNumberFrom(n)) }) };

@Test
public void infinite_perfect_number_sequence() {
  def perfectNumbers = perfectNumbers(nextPerfectNumberFrom(1))
  assertEquals([6, 28, 496], perfectNumbers.getHead(3))
}

Usando o método prepend() , definido na Listagem 5, desenvolvi uma lista de números perfeitos com o valor inicial como início e um bloco de fechamento que sabe como calcular o próximo número perfeito como fim. Eu inicializo minha lista com o primeiro número perfeito depois de 1 (usando uma importação estática para que eu possa chamar meu método NumberClassifier.nextPerfectNumberFrom() com mais facilidade) e peço que minha lista retorne os três primeiros números perfeitos.

O cálculo de novos números perfeitos é dispendioso, portanto eu o faria o mínimo possível. Ao desenvolver como uma lista lenta, posso adiar os cálculos até o momento exato.

É mais difícil pensar sobre as sequências infinitas se sua abstração de "lista" for "slots numerados". Pensar em uma lista como o "primeiro elemento" e o "restante da lista" o incentiva a pensar sobre os elementos na lista em vez de pensar na estrutura, o que, por sua vez, permite que você pense sobre coisas como listas lentas.


Conclusão

Uma das maneiras de os desenvolvedores darem grandes saltos em sua produtividade é o desenvolvimento de abstrações efetivas para ocultar os detalhes. Não chegaríamos a lugar algum se ainda estivéssemos programando com uns e zeros. Um dos aspectos cativantes das linguagens funcionais é a tentativa de abstrair mais detalhes dos desenvolvedores. Linguagens dinâmicas modernas no JVM já proporcionam muitos desses recursos. Neste artigo, eu mostrei como mudar sua perspectiva sobre como o desenvolvimento das listas permite que a linguagem gerencie o estado durante a iteração. Além disso, quando você pensa em listas como "início" e "fim", consegue desenvolver listas lentas e sequências infinitas.

No próximo artigo, você verá como a metaprogramação do Groovy pode tornar seus programas mais funcionais — e permite que você aumente as bibliotecas funcionais de terceiros para que elas trabalhem melhor no Groovy.


Recursos

Aprender

Obter produtos e tecnologias

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 é um arquiteto de software e Meme Wrangler, na ThoughtWorks, uma consultoria global de TI. 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 uma variedade de tecnologias, inclusive The Productive Programmer Seu enfoque é o projeto e construção de aplicativos corporativos de grande porte. Também é orador internacionalmente aclamado nas conferências de desenvolvedores ao redor do mundo. Conheça seu Web site.

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=779731
ArticleTitle=Pensamento Funcional: Recursos funcionais no Groovy, Parte 1
publish-date=12092011

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).