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
defmodule Math do
  def sum_list([]) do
    0
  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 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.

Quando há somente um item na lista, head é igual a esse elemento, e tail é vazia ([]).

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:

Comments