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

Conceitos na prática: Ruby Procs

Nos posts anteriores, foi mostrado um pouco sobre lambdas e blocos em Ruby. No entanto existe mais uma outra estrutura que possui funcionalidade bem similar, mas com diferenças sutis e importantes. Nesse post vou falar um pouco sobre Proc e as as suas diferenças.

O que são Procs?

Se você já leu os posts anteriores sobre lambdas e blocos, o conceito de ter um pedaço de código que pode ser executado e passado como parâmetro para outros métodos não deve ser novidade. Bom, isso é o que um Proc é.

Veja no exemplo a seguir como é a sintaxe para criação de procs:

hello_proc = Proc.new do
  puts "Hello World"
end

hello_proc.call

Ou seja, é bem semelhante a lambdas. E para ficar mais parecido ainda, podemos usar apenas “proc” ao invés de “Proc.new”:

hello_proc = proc do
  puts "Hello World"
end

hello_proc.call

Além da sintaxe, as propriedades de blocos e lambdas são bem semelhantes também, já que um lambda é uma versão especial de um bloco.

hello_lambda = lambda {}
hello_proc = proc {}

hello_proc.class #=> Proc
hello_lambda.class #=> Proc

Então todos aqueles exemplos que vimos no post anterior sobre conversões entre blocos e lambdas também se aplica a Procs. Dito isto, vou tentar focar mais nas diferenças entre blocos, lambdas e procs [1].

Blocos vs. Procs/Lambdas

A primeira grande diferença é que apenas um bloco pode ser passado para um método.

Nos exemplos do post sobre blocos nós vimos que não é necessário explicitar que o bloco será passado, a menos que você precise referenciá-lo dentro do método (para passar para outro método, por exemplo). No entanto não é possível passar mais de um bloco para um mesmo método. Precisamos usar lambdas ou procs para alcanças este objetivo.

Uma outra diferença é a sintaxe. Blocos não podem ser executados chamando o método “call”, mas sim utilizando a palavra chave yield. Tudo bem, essa não é uma grande diferença.

Procs vs. Lambdas

Agora as diferenças começam a ficar interessantes!

Vamos tentar utilizar alguns exemplos, observe os dois pedaços de código abaixo, um usa lambda e outro usa proc:

hello_proc = proc do |msg|
  puts msg
end

hello_proc.call
hello_lambda = lambda do |msg|
  puts msg
end

hello_lambda.call

Qual deve ser o resultado de cada pedaço de código? O mesmo? Note que agora o proc e o lambda estão recebendo um argumento que será exibido no terminal.

Se você tentar executar o primeiro código (com proc) verá que a saída é uma linha vazia, pois o parâmetro “msg” não foi passado. Assim, podemos ver que procs automaticamente atribuem “nil” para variáves cujo valor não tenha sido definido.

Já o segundo código (com lambda) lançará uma exceção “ArgumentError: wrong number of arguments (0 for 1)”! Ou seja, os argumentos são necessários para a execução de um lambda. No entanto, podemos contornar esse problema utilizando valores default para o lambda, como mostra o exemplo a seguir

hello_lambda = lambda do |msg = nil|
  puts msg
end

hello_lambda.call

Outra grande diferença está em como o “return” é tratado. O “return” em um lambda retorna o fluxo de execução para o método onde o lambda foi chamado:

hello_lambda = lambda do |msg|
  return msg
end

def run_hello_block(block)
  puts "Preparando para executar lambda..."
  puts "Lambda em execução: #{block.call("Hello World")}"
  return "Lambda executado com sucesso!"
end

puts run_hello_block(hello_lambda)

Nesse caso, tudo funciona como esperado e todas as mensagens são exibidas. No entanto, o mesmo pedaço de código, agora utilizando proc, tem um resultado bem diferente:

hello_proc = proc do |msg|
  return msg
end

def run_hello_block(block)
  puts "Preparando para executar proc..."
  puts "Proc em execução: #{block.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block(hello_proc)

Ao tentar executar este código veremos uma exceção “LocalJumpError: unexpected return”. Esta exceção é lançada quando tentamos executar um return dentro de um argumento [2], exceto quando o argumento é um lambda.

Uma maneira de resolver o problema seria definir o proc dentro do método, assim ele não seria mais um parâmetro e poderia retornar algo:

def run_hello_block
  hello_proc = proc do |msg|
    return msg
  end

  puts "Preparando para executar proc..."
  puts "Proc em execução: #{hello_proc.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block

Com o código acima podemos ver que é possível utilizar o return dentro de um proc. O problema é como o escopo é gerenciado. O proc foi criado fora do escopo do método “run_hello_block”, assim quando ele tenta retornar, e o escopo mudou, a exceção é lançada.

Outra solução bem simples é não utilizar a palavra-chave “return”, já que em ruby a última linha sempre é retornada:

hello_proc = proc do |msg|
  msg
end

def run_hello_block(block)
  puts "Preparando para executar proc..."
  puts "Proc em execução: #{block.call("Hello World")}"
  return "Proc executado com sucesso!"
end

puts run_hello_block(hello_proc)

Existem várias outras diferenças entre Blocos, Procs e Lambdas que não vou falar aqui, mas essas são as principais. Espero que os exemplos possam lhe ajudar a entender melhor essas diferenças.

Referências

[1] Ahsan Sharafuddin: Closures in Ruby, http://technicalecstasy.net/closures-in-ruby-blocks-procs-and-lambdas/

[2] Robert Sosinski: Understanding Ruby Blocks, Procs and Lambdas http://www.robertsosinski.com/2008/12/21/understanding-ruby-blocks-procs-and-lambdas/

Conceitos na prática: Ruby lambda

Olá! Neste post vou falar sobre lambdas em ruby e mostrar alguns exemplos de sua utilidade. No post anterior, falamos um pouco sobre como blocos podem ser úteis para generalizar a implementação de um código. Hoje vamos brincar um pouco com lambdas e como utilizá-las como blocos.

O que são Lambdas

Em ruby é possível criar métodos. Um método contém um conjunto de instruções que serão executadas quando este for invocado. Nesse sentido, lambdas são bem parecidos com métodos. Vamos ver um pouco da sintaxe:

puts_lambda = lambda do
  puts "lambda"
end

puts_lambda.call

Uma primeira observação é que o lambda foi atribuído para uma variável. Isso mostra que podemos armazenar os lambdas em variáveis para executar depois.

A sintaxe para criação de um lambda é bem simples, basta chamar “lambda” e passar um bloco qualquer. Mais tarde, para executar o bloco, basta chamar o método “call”.

Nas verões mais recentes de ruby, podemos criar lambdas utilizando o operador “->”, que é menor do “lambda” mas que não tão legível. O código a seguir é equivalente ao anterior:

puts_lambda = -> do
  puts "lambda"
end

puts_lambda.call

Lambdas e argumentos

Lambdas também podem receber argumentos, assim como métodos. A diferença é que precisamos adicionar argumentos ao bloco, semelhante a como fizemos com o método “for_each” no post anterior.

puts_lambda = lambda do |msg|
  puts "#{msg}"
end

puts_lambda.call "Lambda!"

Agora qualquer objeto que for passado ao lambda será exibido no console. Se tentarmos executar o lambda sem parâmetros, uma exceção “ArgumentError: wrong number of arguments (0 for 1)” será lançada.

Assim como métodos também podemos atribuir um valor default para os parâmetros que devem ser opcionais.

puts_lambda = lambda do |msg = "lambda"|
  puts "#{msg}"
end

puts_lambda.call
puts_lambda.call "Lambda!"

Lambdas como blocos

Agora que conhecemos a sintaxe do lambda, vamos brincar um pouco. No post anterior vimos que é possível passar um bloco para o método “each” de um array. Pois bem, como lambdas armazenam um bloco, também podemos passar lambdas como blocos:

puts_lambda = lambda do |msg|
  puts "#{msg}"
end

[1,2,3,4,5].each puts_lambda #=>ArgumentError: wrong number of arguments (1 for 0)

Ao executar o código acima, uma exceção será lançada. Pela mensagem podemos ver que o método each não aceita argumentos, pois passamos 1 argumento enquanto nenhum era esperado. Então como podemos passar um bloco como argumento para o “each” se ele não aceita nenhum argumento?

Lembra que no post anterior vimos que não é necessário explicitar que um método em ruby recebe um bloco como argumento? Que basta passar o bloco e executar um “yield” para que o bloco seja chamado?

Pois bem, ao criar um lambda usando o irb, podemos ver que o terminal mostra “Proc:0x007ff52397a8b8@(irb):6 (lambda)” como retorno. Ou seja, lambdas são na verdade objetos da classe Proc! Se tentarmos chamar o método “class” em um objeto lambda temos como retorno “Proc”.

No entanto existem algumas pequenas diferenças entre Proc e lambdas que serão faladas em posts futuros. Por hora precisamos passar o lambda como bloco para o método “each” de um array.

A maneira como podemos converter lambdas (e procs) em blocos é utilizando o caractere “&” logo atrás do objeto. Como no seguinte código:

puts_lambda = lambda do |msg|
  puts "#{msg}"
end

[1,2,3,4,5].each &puts_lambda

Operado Ampersand ou “&”

O operador “&” vai converter objetos proc em bloco ou bloco em proc. A útilidade de converter um bloco em proc é quando você tem um método, que deve receber um bloco como parâmetro e precisa passar esse bloco para outro método.

Imagine que o método “for_each” do post anterior agora vai simplesmente passar o bloco para o método each, ao invés de chamar o yield:

def for_each(array, &block)
  array.each &block
end
 
for_each [1,2,3,4,5] do |e|
  puts e
end

No parâmetro, recebemos o bloco com o operador “&”, dizendo que o bloco deve ser convertido em proc. Depois, com o objeto block em mãos, passamos para o método “each” também utilizando o operador “&”, pois queremos que o objeto proc seja convertido em bloco para o método each.

Agora, se quisermos que o método “for_each” receba um lambda como argumento, não é necessário o primeiro “&” (na lista de argumentos). Como lambda já são procs, não precisamos converter nada e a implementação seria a seguinte:

puts_lambda = lambda do |msg|
  puts "#{msg}"
end

def for_each(array, lambda)
  array.each &lambda
end
 
for_each [1,2,3,4,5], puts_lambda

Apesar de parecer complicado, na prática lambdas ajudam bastante a generalizar código para que fique mais fácil reutilizá-lo. Vale a pena quebrar um pouco a cabeça para entender lambdas.

No próximo post vou falar um pouco sobre Procs e mostrar algumas diferenças dos lambdas. Até lá!

Referências

[1] Ahsan Sharafuddin: Closures in Ruby, http://technicalecstasy.net/closures-in-ruby-blocks-procs-and-lambdas/

Conceitos na prática: Ruby block (yield)

Olá! Esta será uma série de posts que vão apresentar alguns conceitos bem legais da linguagem Ruby e situações onde poderemos aplicá-los para obter um código bem melhor. E o primeiro post será sobre blocos em ruby e a famosa palavra chave yield.

O que são blocos

Em Ruby existem várias definições diferentes de coisas bem semelhantes. Blocos, Lambda e Procs são referenciados de maneira geral como “closure”. Além de dar nome a uma linguagem, closure é uma funcionalidade que permite escrever um pedaço de código que pode ser [1]:

  • Utilizado como um valor (podendo ser atribuído, passado como parâmetro, etc.)
  • Pode ser executado em qualquer lugar
  • Referenciam variáveis no contexto onde são criados

Nos próximos posts eu falo um pouco mais sobre os benefícios da utilização de closure, agora vamos focar na parte prática da coisa.

Passando blocos como argumento

Blocos em ruby são pedaços de código que estão entre “do end” ou entre “{}”. Veja um exemplo:

array.each do |e|
  puts e
end

O código “puts e” que está entre o “do end” é um bloco que foi passado como parâmetro para o each.

Recebendo blocos como argumento

Para exemplificar, vamos tentar criar o nosso próprio método “for_each”, que vai receber um array um bloco que será executado em cada um dos elementos do array:

def for_each(array)
  array.each do |e|
    yield e
  end
end

for_each [1,2,3,4,5] do |e|
  puts e
end

A saída desse código serão os números dentro do array. O que o “for_each” fez foi executar um yield em cada elemento do array. O yield para a execução do método (no caso “for_each”) e passa o controle para o bloco que foi passado como parâmetro pelo método.

Generalização com blocos

Vamos para mais um exemplo. Suponha que você precisa implementar uma lógica para tentar executar um mesmo método (ou qualquer pedaço de código). Digamos que você precise se conectar em um host, mas caso a conexão não possa ser estabelecida vamos tentar 3 vezes.

Ruby já tem um “retry” que pode ser utilizado dentro de um “begin/rescue”, mas não vamos utilizá-lo neste exemplo. Um código para resolver esse problema seria:

retry_count = 0
successful = false
while(!successful or (retry_count < 3))
  begin
    page = Nokogiri::HTML(open("brizeno.wordpress.com"))
    successful = true
  rescue Errno::ECONNRESET => e
    retry_count += 1
  end
end

Com esse código podemos tentar a conexão por 3 vezes. Agora, caso seja necessário aplicar essa lógica em vários lugares, será necessário quebrar um pouco mais a cabeça e garantir que o código é reutilizável.

Graças aos blocos, podemos extrair esse comportamento para um método, que vai receber como parâmetro o número de tentativas a serem realizadas e o que deve ser feito. Seria algo do tipo:

def retry_this times
  retry_count = 0
  successful = false
  while(!successful or (retry_count < times))
    begin
      yield
      successful = true
    rescue Errno::ECONNRESET => e
      retry_count += 1
    end
  end
  raise "Couldn't establish connection" unless sucessful
end

A utilização desse método seria:

retry_this 3 do
  page = Nokogiri::HTML(open("brizeno.wordpress.com"))
end

Um pouco mais sobre blocos

Repare que não é necessário explicitar que o bloco é um parâmetro do método “retry_this”. Em ruby, todo método pode executar um yield e passar o controle para um bloco, caso ele exista. E o que acontece quando um bloco não é passado?

No exemplo anterior, uma exceção seria lançada “no block given (yield) (LocalJumpError)”. Podemos resolver esse problema facilmente, basta adicionar uma outra palavra chave: “block_given?”.

def retry_this times
...
  yield if block_given?
...
end

Agora o bloco só será chamado se existir. Essa é uma boa prática para desenvolver métodos que precisam de blocos, a não ser que o objetivo realmente seja avisar que o bloco não foi passado.

Uma outra curiosidade sobre o blocos é que, no primeiro exemplo, nós fizemos uma chamada ao yield passando um parâmetro. Um bloco pode receber parâmetros utilizando os caracteres “| |”. Ao chamar o “each” nós precisamos receber um argumento: o elemento do array que está sendo iterado atualmente.

Para iterar sobre uma hash por exemplo, precisamos de dois argumentos: a chave e o valor.

my_hash.each do |key, value|
  puts "#{key} => #{value}"
end

O yield dentro do metodo each de hashes provavelmente se parece com o seguinte:

yield key, value if block_given?

Ruby possui vários métodos para iterar em um conjunto de valores, todos eles se utilizam de blocos. O “inject” por exemplo utiliza dois argumentos, um contador e o elemento do conjunto, e adiciona o retorno do bloco ao contador:

acc = [1,2,3,4,5].inject do |accumulator, number|
  accumulator +  number
end
puts acc # => 15 (1 + 2 + 3 + 4 + 5)

Martin Fowler escreveu um artigo [2] bem interessante (na categoria old but gold), sobre a facilidade que os método de iteração proporcionam e os benefícios de se utilizar lambdas. E lambdas serão o tema do próximo post!

Referências

[1] Ahsan Sharafuddin: Closures in Ruby, http://technicalecstasy.net/closures-in-ruby-blocks-procs-and-lambdas/
[2] Martin Fowler: Lambdas, http://martinfowler.com/bliki/Lambda.html