Conceitos na Prática: Programação funcional com Ruby

Se você acompanhou os posts anteriores deve ter visto um pouco sobre como funções podem ser utilizadas como qualquer outro tipo de variável usando lambdas, blocos e Procs.

Ruby é uma linguagem orientada a objetos. Tudo em ruby é um objeto ou pode ser representado como tal. O que faz ruby ter um toque de programação funcional é que funções são cidadãos de primeira classe [1].

Vamos explorar alguns exemplos que mostram a diferença de código entre programar puramente orientado a objetos e usar um pouco de programação funcional em Ruby.

Alguns exemplos simples

Vamos adicionar todos os números de um array e calcular o valor total. Em uma linguagem como Java, podemos fazer algo do tipo:

int soma = 0;
for(int i=0 ; i< array.length ; i++){
  soma += array[i];
}
return soma;

E no final teríamos, na variável soma, o valor total dos elementos do array. O exemplo acima ilustra bem um código orientado a objetos, estamos lendo o estado do objeto array, executando um certo processamento e armazenando o valor em um outro lugar.

Em ruby também é possível fazer esse mesmo laço:

soma = 0
for i in (0..array.length) do
  soma += array[i]
end
soma

Exatamente o mesmo laço. As mesmas observações são válidas. Não existe nenhum problema com esse código, a lógica implementada funciona perfeitamente. No entanto podemos melhorar bastante esse código aplicando algumas das funções que são padrões em ruby.

each

O each é a forma mais simples de iterar sobre um array (ou vários outros objetos). No entanto ele faz uso de funções como parâmetros, o que pode deixar o nosso código mais interessante.

soma = 0
array.each do |e|
  soma += e
end
soma

Não foi uma grande mudança, mas já dá pra notar que o código reduziu. E também conseguimos eliminar uma variável! Nesse caso não é lá grande coisa, mas num cenário real essa redução de complexidade ajuda bastante na manutenção do código.

Nós conseguimos nos livrar da variável que mantém o índice pois agora não precisamos acessar indiretamente (usando o índice) o elemento do array. Dentro do nosso lambda nós temos acesso direto ao elemento e simplesmente dizemos o que deve ser feito e não o como.

inject

Ainda temos uma variável temporária no nosso código que mantém o total da soma dos elementos do array. Podemos usar o inject para nos livrarmos dela:

array.inject(0) do |soma, e|
  soma += e
end

O que o inject faz é, você passa um lambda que aceita dois parâmetros. Um será o acumulador, que guarda o resultado das operações, e o outro será o elemento em si. No final de cada execução do bloco, o resultado do bloco será passado como o acumulador na execução seguinte. Como passamos o 0 como parâmetro, o primeiro valor do acumulador será 0 e em cada interação será o valor atual mais o elemento.

Não nos livramos realmente daquela variável soma, pois ela ainda está ali no nosso código, mas note que agora ela faz parte do lambda, então será gerenciada pelo próprio interpretador e não por nós. Uma coisa a menos para se preocupar.

Além disso, perceba que não precisamos mais dizer que estamos retornando a variável soma no final do método. Primeiro que agora ela pertence ao bloco que é passado ao inject e segundo que o inject já retorna pra gente o valor do acumulador.

De novo, em um caso real isso faz uma boa diferença na manutenção, mas para esse exemplo talvez não seja tão interessante.

O inject, assim como outras funções que aceitam funções como parâmetro pode ser simplificado utilizando um recurso do ruby que transforma um símbolo em uma função:

array.inject(&:+)

Agora sim o código está bem menor. Mas do ponto de vista da manutenção, talvez essa opção não seja muito boa, principalmente se sua equipe não tem muita experiência com ruby. Vamos tentar entender o que está acontecendo aqui com um outro exemplo de uma outra função.

select/reject

Ruby provê duas funções bem interessantes chamadas select e reject. Como o nome diz elas vão selecionar ou rejeitar os elementos do conjunto baseado em um determinado critério:

array = [1,2,3,4]
array.select do |e|
  e.even? # verifica se o número é par
end

O código acima vai retornar um novo array contendo apenas os elementos em que a chamada do método even? retorna verdadeiro. Podemos reduzir esse código utilizando a mesma técnica anterior:

array = [1,2,3,4]
array.select(&:even?)

O que acontece aqui é que estamos passando um símbolo (:even?) com o operador &, a implementação do select sabe que, quando receber um símbolo, deve ir em cada elemento do conjunto e executar o método cujo nome seja o mesmo do símbolo. Assim podemos diminuir nossa implementação e simplificar as coisas.

Com o reject seria a mesma coisa. Para obter apenas os números pares podemos rejeitar os números ímpares:

array = [1,2,3,4]
array.reject(&:odd?)

Voltando para o inject

O que o inject faz quando passamos o símbolo com o & é criar o acumulador com 0 e atribuir para ele o valor atual adicionando o resultado da chamada do método que passamos como símbolo.

No exemplo, como passamos &:+ será feita a adição do elemento ao acumulador. Seria mais ou menos como se estivéssemos executando esse código:

array.inject do |soma, e|
  soma += e
end

É como se, além de ter um valor default para a variável soma (0) o método também tivesse um bloco default que muda apenas qual método será chamado.

Um exemplo mais interessante é o cálculo do fatorial de um número, que pode ser feito em uma linha com a ajuda do inject:

def fatorial_de n
  (1..n).inject(&:*) || 1
end

O que o inject vai fazer nesse caso é jogar no acumular o valor atual dele multiplicado pelo valor do elemento do conjunto. Nesse caso o nosso conjunto vai de 1 até o valor do parâmetro n. O código “|| 1” está ali para evitar que quando um valor menor que 1 é passado seja retornado nil.

Próximos passos

As funções que foram mostradas aqui são apenas um pequeno conjunto. Existem várias outras que valem a pena serem estudadas como o map ou collect, que mapeia os valores do conjunto atual em outro conjunto. Além disso existem várias outras características funcionais que podem ajudar a simplificar o seu código.

Um conjunto de posts que tem uma visão mais aprofundada sobre técnicas de programação funcional aplicadas em Ruby é http://www.sitepoint.com/functional-programming-techniques-with-ruby-part-i/.

Referências

[1] http://en.wikipedia.org/wiki/First-class_function

Anúncios

2 comentários sobre “Conceitos na Prática: Programação funcional com Ruby

  1. Muito bom o posto Marcos. Eu uso bastante o esse tipo de métodos. No caso do inject, eu prefiro usar o reduce, para funções como soma, multiplicação, `reduce(&:+)`. E um detalhes sobre o post em si, acho que faltou uma breve explicação do que o & ta fazendo ali! 😉

    Resumindo, parabéns muito bom!

  2. Nos exemplos com inject, a atribuição no bloco é desnecessária e induz a erro. É o valor da última expressão do bloco que alimentará o valor da variável soma na próxima iteração, não o valor da variável local soma ao fim do bloco.

    Em vez de `ary.inject(0) { |s, k| s += k }`, prefira `ary.inject(0) { |s, k| s + k }`.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s