Anotações do Hercules

Pattern matching em Ruby

26-01-2015

Ruby é uma linguagem com influências de vários paradigmas de programação, dentre eles o funcional. Há 2 características de linguagens funcionais que quando se aprende sente falta em outras linguagens, que são: high order functions e pattern matching. Em Ruby temos high order functions através de blocos, mas infelizmente pattern matching não está presente. Não da forma como Haskell, F#, OCaml e tantas outras linguagens funcionais extensivamente usam.

Ao trabalhar com blocos em Ruby podemos usar alguns padrões para distinguir os parâmetros passados. Escrevemos um bloco com 2 parâmetros assim:

block = -> (one, two) { p one, two }
block.call(1, 2)
# 1
# 2

Aqui não tem mistério. Um bloco que recebe o primeiro e segundo parâmetro e faz alguma coisa. Nesse exemplo apenas imprime os valores na saída padrão.

Vamos ver algo um pouco mais interessante. E se quisessemos que o bloco recebesse um número indefinido de parâmetros? Podemos usar o recurso de splat parameters:

block = -> (*args) { p args }
block.call(3, 4)
# [3, 4]

Legal, né? Ele junta todos os parâmetros em um array, o qual podemos manipular. Dá para ficar melhor! Podemos pegar apenas o primeiro parâmetro e deixar o resto em um array, um padrão bem comum em linguagens como Haskell por exemplo.

block = -> (head, *tail) { p head, tail }
block.call(5, 6, 7, 8)
# 5
# [6, 7, 8]

Se o primeiro ou último parâmetro não importa, podemos ignorá-lo.

block = -> (_, *tail) { p tail }
block.call(5, 6, 7, 8)
# [6, 7, 8]
block = -> (*init, _) { p init }
block.call(5, 6, 7, 8)
# [5, 6, 7]

Também podemos pegar o primeiro e último parâmetro, e deixar os intermediários em um array.

block = -> (first, *middle, last) { p first, middle, last }
block.call(5, 6, 7, 8)
# 5
# [6, 7]
# 8
block.call(5, 8)
# 5
# []
# 8

Com atribuições também conseguimos fazer coisas interessantes.

_, *tail = [1, 2, 3, 4]
p tail
# [2, 3, 4]
head, _  = [1, 2, 3, 4]
p head
# 1
*init, _ = [1, 2, 3, 4]
p init
# [1, 2, 3]

Em linguagens funcionais geralmente usamos tuplas em determinados momentos. Ruby não tem um objeto tupla, mas podemos usar um array de 2 posições para simular uma, e ao iterar numa coleção de tuplas (array de array) temos algumas facilidades para obter esses valores.

[
  ["First Name", "Hercules"], 
  ["Last Name",  "Merscher"] 
].each do |(key, value)|
  puts "#{key}: #{value}"
end
# First Name: Hercules
# Last Name: Merscher

Hashes também não podiam ficar de fora.

{ first_name: "Hercules", last_name: "Merscher" }.each do |(key, value)|
  puts "#{key}: #{value}"
end
# first_name: Hercules
# last_name: Merscher

Com esses truques dá para diminuir um pouquinho mais nosso código, e fica mais fácil de ler e entender.

comments powered by Disqus