Blog do Phil

Tecnologia e opinião

Usando pattern matching e recursividade com Elixir

Uma das linguagens de programação que mais tem me animado ultimamente é a Elixir. Ela possui muitas features legais, e uma delas é o pattern matching.

Patter maching é uma feature, assim como muitas outras, herdada da Erlang, que é a linguagem em que Elixir se basea. Em uma tradução livre, pattern maching quer dizer “casamento de padrão”. O termo é meio exquisito em português, mas vamos a um exemplo:

1
{:ok, name} = {:ok, "Philip"}

Nesse exemplo, estamos “casando” o que está do lado direito com um padrão do lado esquerdo. Explico: do lado esquerdo temos uma tupla com o primeiro elemento :ok e o segundo elemento como uma variável name.

A linguagem vai tentar fazer bater o tipo e o conteúdo dos dois lados. No caso, o tipo bate: – é uma tupla; – o primeiro elemento da tupla bate: :ok é :ok; – o segundo argumento também bate: do lado esquerdo temos uma variável a ser “preenchida”, do lado direito temos um valor para ela.

Agora um exemplo onde o pattern não bate:

1
2
{:ok, "João" } = {:ok, "Philip"}
# => ** (MatchError) no match of right hand side value: {:ok, "Philip"}

Por mais que o tipo do segundo elemento seja o mesmo, não há casamento porque os conteúdos das strings são diferentes.

Usando pattern matching em funções

Um dos usos mais poderosos para o pattern maching é na declaração de funções.

Em Elixir, as funções são definidas por três combinações: o seu nome, o número de argumentos, e o “padrão” desses argumentos. Em suma, são definidas pelo nome e argumentos.

Isso quer dizer que uma função somar(numero1, numero2) é diferente da função somar(numero1, numero2, numero3). E as funções check({200, body}) e check({404, body}) são diferentes por conta do padrão de seus argumentos (a diferença do primeiro elemento da tupla).

Uma aplicação muito legal pra isso é na construção de funções recursivas.

O exemplo a seguir mostra como somar números de uma lista através de recursividade. Ele é bem parecido com o encontrado no guia Getting started do site oficial da linguagem, mas considero o meu (IMHO) um pouco mais simples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
defmodule Math do
  def sum_list([]) do
    0
  end

  def sum_list([number]) do
    number
  end

  def sum_list([head|tail]) do
    head + sum_list(tail)
  end
end

# Exemplos:
#
#   Math.sum_list([10])
#   # => 10
#
#   Math.sum_list([17, 13, 7, 5])
#   # => 42
#
#   Math.sum_list([])
#   # => 0

Como você pode perceber, os nomes das funções são idênticos, mas o que muda são seus argumentos. Todas as três recebem listas, porém cada uma com um padrão diferente:

  • A primeira definição recebe uma lista vazia, e como uma lista vazia não tem números, retorna zero;
  • A segunda definição recebe um único número, e retorna esse número como total;
  • A terceira função recebe uma lista e divide ela em duas partes: cabeça e “calda”.
    • A cabeça (head) é composta por um único elemento, que é o primeiro elemento da lista;
    • A “calda” é o restante da lista. Então uma lista [2, 3, 4] será dividida em head = 2 e tail = [3, 4]. Feito isso, a função ira somar recursivamente o primeiro elemento da lista com o resultado da próxima chamada da função recebendo a lista da calda.

As chamadas recursivas de “sum_list” serão processadas levando em conta o “padrão” dos argumentos passados. A “condição de parada” da terceira função é quando tail for uma lista com somente um elemento. Neste caso a função que será utilizada é a segunda, que recebe uma lista com um elemento.

Para onde ir agora?

Nos exemplos usados aqui vimos um pouco do que é possível fazer com o uso de pattern matching. Essa não é uma característica exclusiva da Elixir, mas para mim ficou muito mais fácil de entender com essa linguagem (já havia tentado com Haskell).

Experimente implementar os exemplos e deixe suas dúvidas e sugestões nos comentários.

Recomendo esses links para mais detalhes da linguagem:

Executando seus testes em uma App Rails na velocidade máxima com Vim e Zeus

Configurei meu Vim para rodar os testes da minha aplicação Rails (dependendo ou não do Rails) bem mais rápidos, através do Zeus.

O Zeus pré-carrega (faz um preload) sua aplicação Rails, e tudo que é dependência dela. Você pode instalá-lo com o comando:

1
$ gem install zeus

É importante lembrar que o Zeus não deve ser declarado no seu Gemfile.

Para executar o Zeus, vá a seu projeto Rails e execute zeus start. A partir daí, você conseguirá subir seu servidor (zeus s) ou executar comandos (zeus rake/generate/etc) de forma muito rápida!

Para rodar seus testes usando RSpec direto do seu Vim, você pode usar o plugin vim-rspec. Instale-o pela maneira mais recomendada, usando Vundle ou a que você preferir.

Em seguida é necessário configurar o comando que executa os testes. Adicione a linha abaixo ao seu .vimrc:

1
let g:rspec_command = "! zeus rspec {spec}"

O comando padrão para executar os testes (todos do arquivo) é \\t (<Leader>t) Se quiser executar um teste específico, mova o cursos para ele e execute \\s(<Leader>s). Há também um comando para executar o último teste: \\l(<Leader>l)).

Com o rspec-vim sua produtividade pode aumentar consideravelmente, evitando a mudança de abas ou terminais para executar seus códigos.

Declaração de variável no Ruby: por valor ou referência?

Toda declaração de variável na linguagem Ruby é feita por referência. Isso significa que quando declaramos uma variável, na verdade estamos declarando uma referência para um objeto em memória. Essa informação parece ser básica e dificilmente você terá problemas com isso. Mas aconteceu comigo, onde por engano eu esqueci deste pequeno detalhe.

Meu caso foi com um código mais ou menos como o abaixo.

1
2
3
4
5
6
7
8
9
a = [1, 2, 3, 4]
b = a
b << 5
puts b
#=> [1, 2, 3, 4, 5]
puts a
#=> [1, 2, 3, 4, 5]
puts a.object_id == b.object_id
#=> true

O código não era tão simples, mas o exemplo ilustra bem como aconteceu. Meu intuito era trabalhar com o conteúdo “armazenado” em b e deixar o conteúdo em a intacto. Porém quando alterei o valor de b (adicionando 5), também alterei em a. Isso causou um bug que foi corrigido no processo de code review.

Isso porque em Ruby todas as declarações são feitas através de referências para objetos.

Para resolver possíveis problemas (como alterar acidentalmente o objeto errado), você pode “clonar” ou “duplicar” (veja a diferença nos links abaixo) um objeto quando for referenciá-lo a uma variável.

Exemplo:

1
2
3
4
5
6
7
8
9
a = [1, 2, 3, 4]
b = a.dup
b << 5
puts b
#=> [1, 2, 3, 4, 5]
puts a
#=> [1, 2, 3, 4]
puts a.object_id == b.object_id
#=> false

Alguns links do Stack Overflow sobre o assunto:

Estatísticas de submissões no SPOJ

Por volta de setembro do ano passado iniciei o desenvolvimento de uma app para mostrar estatísticas e promover desafios entre usuários(equipes) do SPOJ.

Percebi que já existiam algumas pequenas aplicações para estatísticas, mas nada que promovesse desafios entre usuários(times). Eu queria unir as duas características. Mas o tempo passou e essa app acabou ficando de lado.

Foi então que decidi lançar o que foi feito, mesmo com alguns bugs.

O site fica no Heroku, e você pode acessar aqui. Não sei se vou continuar desenvolvendo essa aplicação, mas já valeu a pena pois aprendi muito com isso!