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:
- Pegue um valor inicial e combine-o por meio de uma operação no primeiro elemento da lista.
- Pegue o resultado e aplique a mesma operação no próximo elemento.
- 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.
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.
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.
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.
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.
Aprender
- The Productive Programmer (Neal Ford, O'Reilly Media, 2008): O livro mais recente de Neal Ford trata de ferramentas e práticas que ajudam a melhorar sua eficiência em codificação.
- Scala: é uma linguagem moderna e funcional em JVM.
- Functional Java: é uma estrutura que acrescenta muitas construções de linguagem funcional a Java.
-
Guia do Scala para desenvolvedores de Java atarefados: aprofunde-se em Scala nessa série do developerWorks escrita por Ted Neward.
-
"Practically Groovy: Functional programming with curried closures" (Ken Barclay et al., developerWorks, agosto de 2005): leia mais sobre programação funcional com Groovy.
- Design Patterns: Elements of Reusable Object-Oriented Software (Erich Gamma et al., Addison-Wesley, 1994): a obra clássica da Gang of Four sobre padrões de design.
-
Navegue pela livraria de tecnologia para ver livros sobre este e outros tópicos técnicos.
-
Zona tecnologia Java do developerWorks: Encontre centenas de artigos sobre quase todos os aspectos da programação Java.
Obter produtos e tecnologias
- ScalaTest: é uma biblioteca de teste de unidade para código Scala e Java.
- Faça o download de Versões de avaliação de produto IBM ou explore as versões de teste on-line no IBM SOA Sandbox e entre em contato com as ferramentas de desenvolvimento de aplicativos e produtos de middleware do DB2®, Lotus®, Rational®, Tivoli®eWebSphere®.
Discutir
-
Confira blogs do developerWorks e participe da comunidade do developerWorks.

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.