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: Pensando funcionalmente, Parte 3

Filtragem, teste de unidade e técnicas de reutilização de código

Neal Ford, Software Architect / Meme Wrangler, ThoughtWorks Inc.
Photo of 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:  O autor da série Pensamento Funcional Neal Ford continua sua visita guiada pelas construções e paradigmas de programação funcional. Veremos um código de número-classificação em Scala e daremos uma olhada breve no teste de unidade no mundo funcional. Em seguida, aprenderemos sobre a aplicação parcial e currying — duas abordagens funcionais que facilitam a reutilização de código — e veremos como a recursão se encaixa na forma funcional de pensar.

Visualizar mais conteúdo nesta série

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

Na primeira e na segunda partes de Pensamento Funcional, falei de alguns tópicos de programação funcional e como se relacionam com Java™ e suas linguagens relacionadas. Esta parte continua essa exploração, mostrando uma versão Scala do classificador de número das partes anteriores e tratando de alguns tópicos com um toque acadêmico, como currying, aplicação parcial e recursão.

Classificador de número em Scala

Guardei uma versão Scala do classificador de número por último porque é a que tem menos mistérios sintáticos, pelo menos para desenvolvedores Java. (Recapitulando os requisitos do classificador: Dado qualquer número inteiro positivo maior que 1, é necessário classificá-lo como perfeito, abundante ou deficiente. Um número perfeito é aquele cujos fatores, excluindo o número em si como fator, somam-se ao número. A soma dos fatores de um número abundante é maior que o número, e a de um número deficiente é menor). A Listagem 1 mostra a versão de Scala:


Listagem 1. Classificador de número em Scala

package com.nealford.conf.ft.numberclassifier

object NumberClassifier {

  def isFactor(number: Int, potentialFactor: Int) =
    number % potentialFactor == 0

  def factors(number: Int) =
    (1 to number) filter (number % _ == 0)

  def sum(factors: Seq[Int]) =
    factors.foldLeft(0)(_ + _)

  def isPerfect(number: Int) =
    sum(factors(number)) - number == number

  def isAbundant(number: Int) =
    sum(factors(number)) - number > number

  def isDeficient(number: Int) =
    sum(factors(number)) - number < number
}  

Mesmo que o Scala nunca tenha sido visto até agora, esse código é bem legível. Como antes, os dois métodos que nos interessam são factors() e sum(). O método factors() pega a lista de números de 1 até o número de destino e aplica o método filter() integrado a Scala, usando o bloco de código à direita como critério de filtragem (também conhecido como predicado). O bloco de códigos aproveita o parâmetro implícito de Scala, que permite um marcador sem nome (o caractere _ ) quando uma variável nomeada não é necessária. Graças à flexibilidade sintática de Scala, é possível chamar o método filter() da mesma maneira que se chama um operador. Se preferir, (1 to number).filter((number % _ == 0)) também funciona.

O método sum() usa a operação fold left agora familiar (em Scala, implementada como o método foldLeft() ). Não preciso nomear as variáveis nesse caso, assim uso _ como marcador, o que aproveita a sintaxe simples e limpa para definir um bloco de códigos. O método foldLeft() executa a mesma tarefa do método da biblioteca Functional Java de nome semelhante (veja Recursos), que apareceu no primeiro artigo:

  1. Pegue um valor inicial e combine-o por meio de uma operação no primeiro elemento da lista.
  2. Pegue o resultado e aplique a mesma operação no próximo elemento.
  3. Continue fazendo isso até que a lista esteja esgotada.

Essa é uma versão generalizada de como aplicar uma operação, como adição, a uma lista de números: comece com zero, adicione o primeiro elemento, pegue esse resultado e adicione-o ao segundo e continue até que a lista tenha sido consumida.

Teste de unidade

Embora eu não tenha mostrado os testes de unidade das versões anteriores, todos os exemplos têm testes. Uma biblioteca de testes de unidade efetiva chamada ScalaTest está disponível para Scala (veja Recursos). A Listagem 2 mostra o primeiro teste de unidade que escrevi para verificar o método isPerfect() da Listagem 1:


Listagem 2. Teste de unidade para o classificador de número de Scala

@Test def negative_perfection() {
  for (i <- 1 until 10000)
    if (Set(6, 28, 496, 8128).contains(i))
      assertTrue(NumberClassifier.isPerfect(i))
    else
      assertFalse(NumberClassifier.isPerfect(i))
}  

Como você, eu estou tentando aprender a pensar de forma mais funcional, e o código na Listagem 2 me incomodava de duas maneiras. Primeiro, ele itera para fazer algo, o que exibe pensamento imperativo. Segundo, eu não ligo para o binário que pega tudo da instrução if . Qual problema estou tentando resolver? Preciso ter certeza de que meu classificador de número não identifique um número imperfeito como perfeito. A Listagem 3 mostra a solução para esse problema, declarada de forma um pouco diferente:


Listagem 3. Teste alternativo para a classificação de número perfeito

@Test def alternate_perfection() {
  assertEquals(List(6, 28, 496, 8128),
              (1 until 10000) filter (NumberClassifier.isPerfect(_)))
}  

A Listagem 3 afirma que os únicos números de 1 a 100.000 que são perfeitos são aqueles na lista de números perfeitos conhecidos. Pensar funcionalmente se estende não apenas ao seu código, mas também para a maneira de pensar sobre o teste.


Aplicação parcial e currying

A abordagem funcional que mostrei para filtrar listas é comum em todas as linguagens de programação e bibliotecas funcionais. Usar a capacidade de passar o código como parâmetro (como no método filter() na Listagem 3) ilustra como pensar em reutilização de código de uma maneira diferente. Quem vem de um mundo tradicional orientado a objetos e acionado por designs e padrões deve comparar essa abordagem com o padrão de projeto do Método de Modelo do livro da Gang of Four chamado Design Patterns (veja Recursos). O padrão de Método de Modelo define o esqueleto de um algoritmo em uma classe base, usando métodos abstratos e substituição para adiar os detalhes individuais de classes-filhas. Usar a abordagem funcional de composição permite passar funcionalidade aos métodos que aplicam adequadamente essa funcionalidade.

Outra maneira de conseguir a reutilização de código é via currying. O currying, que recebeu seu nome em homenagem ao matemático Haskell Curry (a linguagem de programação Haskell também recebeu seu nome em homenagem a ele), transforma uma função multiargumentos para que possa ser chamada como uma cadeia de funções de argumento único. Intimamente relacionada é a técnica de aplicação parcial que designa um valor fixo a um ou mais argumentos de uma função, produzindo assim outra função de aridade menor (o número de parâmetros da função). Para entender a diferença, comece olhando o código Groovy na Listagem 4, que ilustra o currying:


Listagem 4. Currying em Groovy

def product = { x, y -> return x * y }

def quadrate = product.curry(4)
def octate = product.curry(8) 

println "4x4: ${quadrate.call(4)}"
println "5x8: ${octate(5)}"

Na Listagem 4, defino product como um bloco de códigos que aceita dois parâmetros. Usando o método integrado curry() de Groovy, uso product como bloco de construção para dois novos blocos de códigos: quadrate e octate. O Groovy facilita chamar um bloco de códigos: é possível executar explicitamente o método call() ou usar a facilidade sintática fornecida em nível de linguagem de colocar um conjunto de parênteses que contém qualquer parâmetro após o nome do bloco de código (como em octate(5), por exemplo).

A aplicação parcial é uma técnica mais ampla que se assemelha ao currying, mas não restringe a função resultante a um único argumento. O Groovy usa o método curry() para lidar com currying e a aplicação parcial, como mostrado na Listagem 5:


Listagem 5. Aplicação parcial versus currying, ambos usando o método curry() de Groovy.

def volume = { h, w, l -> return h * w * l }
def area = volume.curry(1)
def lengthPA = volume.curry(1, 1) //partial application
def lengthC = volume.curry(1).curry(1) // currying

println "The volume of the 2x3x4 rectangular solid is ${volume(2, 3, 4)}"
println "The area of the 3x4 rectangle is ${area(3, 4)}"
println "The length of the 6 line is ${lengthPA(6)}"
println "The length of the 6 line via curried function is ${lengthC(6)}"

O bloco de código volume na Listagem 5 calcula o volume cúbico de um sólido retangular usando uma fórmula conhecida. Criei, então, um bloco de código area (que calcula a área do retângulo) fixando a primeira dimensão de volume (h, que representa a altura) como 1. Para usar volume como bloco de construção para um bloco de código que retorna o comprimento de um segmento de linha, posso executar aplicação parcial ou currying. lengthPA usa aplicação parcial fixando cada um dos dois primeiros parâmetros em 1. lengthC aplica currying duas vezes para fornecer o mesmo resultado. A diferença é sutil, e o resultado final é o mesmo, mas se usarmos os termos currying e aplicação parcial indistintamente e um programador funcional o ouvir, pode ter certeza de que será corrigido.

A programação funcional fornece blocos de construção novos e diferentes para alcançar os mesmos objetivos que as linguagens imperativas realizam com outros mecanismos. Os relacionamentos entre esses blocos de construção são bem pensados. Antes, mostrei a composição como um mecanismo de reutilização de código. Não é de surpreender que se possa combinar currying e composição. Veja o código Groovy na Listagem 6:


Listagem 6. Composição de aplicação parcial

def composite = { f, g, x -> return f(g(x)) }
def thirtyTwoer = composite.curry(quadrate, octate)

println "composition of curried functions yields ${thirtyTwoer(2)}"

Na Listagem 6, criei um bloco de código composite que compõe duas funções. Usando esse bloco de códigos, criei o bloco de códigos thirtyTwoer , usando a aplicação parcial para compor os dois métodos juntos.

Usando aplicação parcial e currying, alcançamos objetivos semelhantes aos mecanismos como o padrão de design de Método de Modelo. Por exemplo, podemos criar um bloco de códigos incrementer construindo-o sobre um bloco de códigos adder , como mostrado na Listagem 7:


Listagem 7. Diferentes blocos de construção

def adder = { x, y -> return x + y }
def incrementer = adder.curry(1)

println "increment 7: ${incrementer(7)}"

Naturalmente, Scala suporta currying, como ilustrado pelo fragmento da documentação de Scala mostrada na Listagem 8:


Listagem 8. Currying em Scala

object CurryTest extends Application {

  def filter(xs: List[Int], p: Int => Boolean): List[Int] =
    if (xs.isEmpty) xs
    else if (p(xs.head)) xs.head :: filter(xs.tail, p)
    else filter(xs.tail, p)

  def dividesBy(n: Int)(x: Int) = ((x % n) == 0)

  val nums = List(1, 2, 3, 4, 5, 6, 7, 8)
  println(filter(nums, dividesBy(2)))
  println(filter(nums, dividesBy(3)))
}

O código da Listagem 8 mostra como implementar um método dividesBy() usado por um método filter() . Passo uma função anônima para o método filter() , usando currying para corrigir o primeiro parâmetro do método dividesBy() para o valor usado para criar o bloco de códigos. Quando passo o bloco de códigos criado usando meu número de destino como um parâmetro, o Scala executa currying em uma nova função.


Filtragem via recursão

Outro tópico intimamente associado à programação funcional é recursão, que (segundo a Wikipédia) é o "processo de repetição de um objeto de um jeito similar ao que já fora mostrado". Na realidade, é uma maneira à moda da ciência da computação de iterar as coisas, chamando o mesmo método a partir de si mesmo (sempre com o cuidado de assegurar que exista uma condição de saída). Muitas vezes, a recursão resulta em código fácil de entender porque o núcleo do seu problema é a necessidade de fazer a mesma coisa vez após vez em uma lista cada vez menor.

Veja o caso de uma filtragem de lista. Usando uma abordagem iterativa, aceito o critério de filtragem e o loop do conteúdo, filtrando os elementos que eu não quero. A Listagem 9 mostra uma implementação simples de filtragem com o Groovy:


Listagem 9. Filtragem em Groovy

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

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

l = filter(1..20, modBy2)
println l  

O método filter() na Listagem 9 aceita list e criteria (um bloco de códigos que especifica como filtrar a lista) e itera na lista, acrescentando cada item a uma nova lista se ele corresponde ao predicado.

Agora, olhe novamente a Listagem 8, que é uma implementação recursiva da funcionalidade de filtragem em Scala. Ela segue um padrão comum em linguagens funcionais para lidar com uma lista. Uma visualização de uma lista é que ela consiste de duas partes: o item na frente da lista (a cabeça) e todos os outros itens. Muitas linguagens funcionais têm métodos específicos para iterar listas usando esse idioma. O método filter() primeiro verifica se a lista está vazia — a condição de saída extremamente importante para esse método. Se a lista estiver vazia, simplesmente retorne. Caso contrário, use a condição de predicado (p) passada como parâmetro. Se essa condição for verdadeira (ou seja, quero esse item na minha lista), retorno uma nova lista construída pegando a cabeça atual e um resto filtrado da lista. Se a condição do predicado falha, retorno uma nova lista que consiste em apenas o resto filtrado (eliminando o primeiro elemento). Os operadores de construção de lista em Scala tornam as condições de retorno em ambos os casos bastante legíveis e de fácil compreensão.

Acho que a maioria não usa recursão no momento — nem faz parte da sua caixa de ferramentas. No entanto, parte da razão está no fato de que a maioria das linguagens imperativas tem um suporte sem graça, tornando-as mais difíceis de usar do que deveria ser. Acrescentando sintaxe limpa e suporte, as linguagens funcionais fazem da recursão uma candidata para a reutilização simples de código.


Conclusão

Neste artigo, continuei minha pesquisa de recursos no mundo do pensamento funcional. Coincidentemente, a maior parte deste artigo foi sobre filtragem, mostrando diversas maneiras de usá-la e implementá-la. Mas isso não é uma grande surpresa. Muitos paradigmas funcionais são construídos em torno de listas porque grande parte da programação se resume a lidar com listas de coisas. Faz sentido criar linguagens e estruturas que têm facilidades extras para listas.

No próximo artigo, vou concluir meu passeio pelos paradigmas de programação funcional.


Recursos

Aprender

Obter produtos e tecnologias

Discutir

Sobre o autor

Photo of 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=742789
ArticleTitle=Pensamento Funcional: Pensando funcionalmente, Parte 3
publish-date=07202011
author1-email=nford@thoughtworks.com
author1-email-cc=jaloi@us.ibm.com

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).