Para começar, podemos dizer que blocos são funções anônimas, ou seja, um trecho de código destinado a realizar algo, mas que não possui um nome ou definição. Tal recurso é geralmente utilizado quando escrevemos algum código que será utilizado apenas uma vez, poucas vezes ou em um contexto específico.

É um código que não será reutilizado, então não vemos sentido em criar uma função nomeada, definir um método, incluí-lo em uma classe, ocupar espaço em memória com a sua definição e coisas do tipo.

Para entender melhor, vamos explorar o que blocos são na prática.

Como Funcionam os Blocos no Ruby

Blocos são uma forma de passar pedaços de código durante a execução de algo. É muito comum ver, por exemplo:

[sourcecode language=”ruby”]
numbers = [1, 2, 3, 4]

numbers.each do |number|
puts number
end
[/sourcecode]

Tal código escreverá na tela:

1
2
3
4

Para quem está acostumado com outras linguagens, uma comparação para ajudar a entender o que está acontecendo, seria este exemplo em JavaScript:

[sourcecode language=”javascript”]
var numbers = [1, 2, 3, 4];

numbers.forEach(function(number) {
console.log(number);
});
[/sourcecode]

Uma característica comum em funções anônimas, é que elas podem ser escritas em uma única linha, o que no Ruby ficaria:

[sourcecode language=”ruby”]
[1, 2, 3, 4].each { |number| puts number }
[/sourcecode]

Um exemplo bem simples de utilização seria somar todos os números dentro de uma array:

[sourcecode language=”ruby”]
total = 0

numbers = [20, 10, 30]

numbers.each { |number| total += number }

puts total
[/sourcecode]

Que escreverá na tela:

60

Isto, fazendo uma última comparação, seria em JavaScript o equivalente a:

[sourcecode language=”javascript”]
var total = 0;

var numbers = [20, 10, 30];

numbers.forEach(function(number) { total += number; });

console.log(total);
[/sourcecode]

Algo interessante é que podemos passar blocos como variáveis de um método. Se quisermos por exemplo um método que ordene uma array alfabéticamente e depois faça algo customizado com cada item da array de acordo com o bloco passado, podemos utilizar o yield:

[sourcecode language=”ruby”]
def sort_and_then(my_list)
my_list = my_list.sort

my_list = my_list.map do |value|
yield(value)
end

puts my_list
end

fruits = [‘Uva’, ‘Abacate’, ‘Banana’]

sort_and_then(fruits) do |value|
value.upcase
end
[/sourcecode]

Tal código escreverá:

ABACATE
BANANA
UVA

Neste caso, o método sort_and_then espera que um bloco sempre seja passado. Se quisermos deixar isto opcional, podemos adicionar uma verificação com o block_given?, ficando assim:

[sourcecode language=”ruby”]
def sort_and_then(my_list)
my_list = my_list.sort

if block_given?
my_list.each { |value| puts yield(value) }
end
end

fruits = [‘Uva’, ‘Abacate’, ‘Banana’]

sort_and_then(fruits)
[/sourcecode]

Perceba que quando o método é chamado sem o bloco não haverá problemas, resultando em:

Abacate
Banana
Uva

Com este código, podemos sempre mudar o bloco passado. Se quisermos ver apenas a primeira letra de cada fruta na array, poderíamos chamá-lo da seguinte maneira:

[sourcecode language=”ruby”]
sort_and_then(fruits) do |fruit|
fruit[0]
end
[/sourcecode]

Resultando em:

A
B
U

Uma outra forma de utilizar blocos como parâmetros além do yield é nomeando o parâmetro com o identificador &. Desta forma utilizamos o método .call para executar o bloco, deixando o código assim:

[sourcecode language=”ruby”]
def sort_and_then(my_list, &custom_block)
my_list = my_list.sort

if block_given?
my_list = my_list.map do |value|
custom_block.call(value)
end
end

puts my_list
end
[/sourcecode]

Um detalhe importante desta abordagem, é que o parâmetro com & (que poderá receber o bloco) deve ser sempre o último do método. Isto é necessário, pois caso contrário seria muito estranho tentar utilizar o do para passar o bloco antes do final da chamada, não daria muito certo tentar fazer algo como:

[sourcecode language=”ruby”]
# bad idea!
def sort_and_then(my_list, &custom_block, other_param)
# …
end

fruits = [‘Uva’, ‘Abacate’, ‘Banana’]

sort_and_then(fruits) do |fruit|
fruit.upcase
end, ‘param 3’

sort_and_then(fruits) { |fruit| fruit.upcase}, ‘param 3’
[/sourcecode]

Por isso a regra é clara, se quiser nomear o bloco recebido com &, ele será sempre o último parâmetro do método e podemos receber apenas um bloco por método.

Resumindo, podemos chamar o nosso método que recebe blocos com trechos de códigos das seguintes maneiras:

[sourcecode language=”ruby”]
fruits = [‘Uva’, ‘Abacate’, ‘Banana’]

sort_and_then(fruits) do |fruit|
fruit.upcase
end

sort_and_then(fruits) { |fruit| fruit.upcase}
[/sourcecode]

Se você utiliza Ruby on Rails, provavelmente já precisou criar helpers para manipular o HTML de suas views.

Uma forma interessante de usar blocos neste cenário seria criar um método para envolver um trecho de HTML com a utilização do capture.

Você poderia criar em seu helper um método como:

[sourcecode language=”ruby”]
def my_custom_box(id, &block)
html = "<div class=’my-box’ id=’#{id}’>"
html += capture(&block)
html += ‘</div>’
html.html_safe
end
[/sourcecode]

E na sua view utilizá-lo da seguinte maneira:

[sourcecode language=”ruby”]
<%= my_custom_box(‘my-id’) do %>
<h1>My Title</h1>
<% end %>
[/sourcecode]

Tal código resultaria no seguinte HTML:

[sourcecode language=”html”]
<div class=’my-box’ id=’my-id’>
<h1>My Title</h1>
</div>
[/sourcecode]

E por aí vai. Existem infinitas aplicações para blocos e com certeza você vai usá-los muito sempre que programar no Ruby.

Então Blocos não são Closures?

Como vimos, blocos são funções anônimas. Porém, isto não quer dizer que blocos não podem ser closures também. Closures são funções que conseguem acessar e manipular variáveis externas, presentes em sua função exterior.

Vamos analisar o seguinte exemplo:

[sourcecode language=”ruby”]
total = 0

def add_to_total(value)
total += value
end

add_to_total(20)

puts total
[/sourcecode]

Tal código gera o erro “undefined method `+' for nil:NilClass”. O motivo deste erro é que o método add_to_total não possui acesso à variável total. Ou seja, neste cenário, add_to_total não é uma closure.

E se passarmos a variável total para ele? Vejamos:

[sourcecode language=”ruby”]
total = 0

def add_to_total(total, value)
total += value
end

add_to_total(total, 20)

add_to_total(total, 10)

puts total
[/sourcecode]

O erro não acontecerá mais, porém o resultado final ao imprimir total será 0. Ele recebeu a variável, mas a manipulou dentro do seu contexto, sem influenciar a variável externa. Novamente, isto mostra que não podemos chamar o add_to_total de closure.

Closures conseguem manipular variáveis externas presentes nas funções que a executam. E blocos conseguem fazer isso? Vamos alterar o código para utilizar um bloco e descobrir:

[sourcecode language=”ruby”]
def add_to_total(value, &my_block)
my_block.call(value)
end

add_to_total(20) { |value| total += value }

add_to_total(10) { |value| total += value }

puts total
[/sourcecode]

O resultado? 30. Então sim, blocos conseguem acessar e manipular variáveis fora do seu contexto, logo, blocos são Closures também.

Concluindo

Blocos são Funções Anônimas e Closures, pois atendem à definição de ambas as coisas.

Eles são amplamente utilizados no Ruby e se bem aplicados podem colaborar muito para deixar o seu código mais versátil e legível.

Blocos são simples e algo que você não pode fazer com eles por exemplo é associá-los à uma variável:

[sourcecode language=”ruby”]
my_block = { puts ‘test’ }
[/sourcecode]

Tal código irá gerar um erro "(syntax error, unexpected tSTRING_BEG)".

Porém, isto é possível quando passamos a utilizar Procs ou Lambdas que são outras formas de se trabalhar com blocos no Ruby. Podemos falar sobre elas em um outro post. =)

E é isso, esperto ter ajudado a esclarecer este assunto.

Um último aviso: Utilize blocos com moderação, de forma ponderada. Não é legal ver por aí códigos assim, com infinitos blocos dentro de outros blocos:

[sourcecode language=”ruby”]
fruits = [‘Uva é Roxa’, ‘Abacate é Verde’, ‘Banana é Amarela’]

def check_fruits(fruits)
fruits.each do |fruit|
words = fruit.split(‘ ‘)
words.each do |word|
letters = word.split(”)

letters.each do |letter|
(‘a’..’z’).each do |letter_to_check|
if letter.casecmp(letter_to_check) == 0
puts "#{letter} is eql #{letter_to_check}!"
end
end
end
end
end
end

check_fruits(fruits)
[/sourcecode]

O rubocop e talvez algumas pessoas podem ficar ofendidas. ;)