Elixir: coleções, sigils e padrões

Olá!

No artigo anterior exploramos como subir o projeto dos Koans e experimentamos com algumas funcionalidades básicas do Elixir.

Vamos continuar?

Os itens que iremos abordar nesse artigo são do 7 até o 12:

lista de features

Keywords Lists: É apenas um açúcar sintático de uma lista de tuplas. Vale mencionar as seguintes características: as chaves precisam ser atoms, ordenadas e podem se repetir. Segue exemplo:

A recomendação oficial da documentação da linguagem é que esse tipo deve ser usada para passagem de parâmetros opcionais entre funções. Pode ser manipulado pelo módulo Keyword. Todas as operações efetuadas pelos módulos Enum e List também se aplicam a esse tipo. Um detalhe interessante que percebi é que é possível utilizar-se de técnicas de Lazy Evaluation apenas utilizando a biblioteca padrão do módulo Keyword. Achei a forma de se trabalhar com Lazy Evaluation mais simples que em outras linguagens (como C#, por exemplo).

Maps: Nada mais é que o tipo recomendado para tratar de estrutura de dados que estejam no formato chave-valor. Apesar de serem bastante parecidos com tuplas comuns, a sintaxe para declarar um map é um pouco diferente. Alguns detalhes que vale a pena mencionar: maps podem ser usados para representar uma espécie de objeto anônimo, não tem ordenação e qualquer coisa pode ser uma chave:

Exemplo de declaração de um map como se fosse um objeto da classe person

Não vi nada de exótico na biblioteca padrão desse tipo e me pareceu bastante simples de usar.

MapSets: Mais um tipo para manipulação de listas, porém, o diferencial dele é que ele não permite duplicados e mantém a ordenação dos elementos até 32 elementos. Do número 33 em diante, internamente esse tipo se transforma em um Hash Array Mapped Trie (HAMT) e as propriedades de ordenação desaparecem, mais detalhes aqui.

Aqui vale explicar o que diabos é um HAMT: é um array que possui propriedades de um hash table (como chaves únicas para garantir lookups O(1) e suscetível a hash collisions da mesma forma). Além de propriedades de um trie, que é uma estrutura de dados hierárquica que pode ser representada graficamente da seguinte forma:

exemplo de uma trie

Qualquer semelhança com árvores binárias não é mera coincidência, dado que é uma estrutura de dados que parte da mesma ideia fundamental como ponto de partida. Porém, ao invés de nós com números são utilizados caracteres. A título de curiosidade, são tries que permitem algoritmos performáticos de autocomplete no teclado do seu smartphone, por exemplo.

E porque o HAMT é utilizado? Performance, oras. Ele é bem econômico em termos de uso de memória comparado a outras estruturas de dados do tipo tree). Aqui vale uma observação empírica que percebi: a maioria das estruturas de dados básicas (como stacks, tree, queues e afins), tem alguns problemas estruturais que acabaram incentivando a criação de outras estruturas mais eficientes em determinados cenários que são mais complexas que as originais, portanto, vale o estudo dessas estruturas especializadas.

Jovens, não existe nada novo na TI: algumas dessas estruturas já estão documentas desde a década de 70/80! Jovens de front-end: se você acha que estruturada de dados não serve para front-end, pesquise aí como DOM / Shadow DOM funcionam.

Para finalizar: tenha bastante atenção ao usar MapSets, pois podem gerar bugs difíceis de lidar caso sejam usados sem levar em consideração os detalhes mencionados acima.

Structs: Se você precisar de um pouco mais de “sensação de estrutura” no seu código que maps puros (e anônimos) não podem te dar, utilize esse tipo. A diferença básica é que você precisa declarar uma struct dentro de um module (foi o que mais me pareceu “fortemente tipado” no Elixir até agora). Se não ficou claro lendo os artigos anteriores, Elixir é uma linguagem de tipagem dinâmica (como JS ou Python).

Segue um exemplo para ilustrar esse “sensação de estrutura”:

Exemplo de declaração de struct

Sim, também achei que é basicamente um classe. É tão parecido que caso você não declare o valor padrão de alguma “propriedade” (o termo correto não é esse, pois estamos utilizando um map e maps não possuem propriedades no sentido estrito do termo, mas vale a menção para fins didáticos nesse caso), o valor assumido é null (devido a influência de Ruby na criação do Elixir, a palavra reservada é nil para representar nulos, assim como em Ruby).

Sigils: São mecanismos da linguagem Elixir para manipulação de representações textuais (ou seja, strings). São compostos da seguinte estrutura: til (~)+ letra que representa o sigil + delimitador.

O sigil~r define expressões regulares (ou regex, para os íntimos):

Os delimitadores são bem flexíveis, podendo ser usados até 8 delimitadores diferentes para expressar uma sigil:

Além de regular expressions, podem ser usados sigils para gerar strings comuns, lista de caracteres (charlists), lista de palavras separadas por espaços (wordlists), datas simples (AAAA-MM-DD), tempo (HH:MM:SS) e datas no formato UTC (com ou sem timezone). Segue exemplos:

O lema do Elixir é “simplicidade e extensibilidade”, portanto, é possível expandir o uso de sigils e criar mecanismos customizados facilmente, mais detalhes aqui.

Pattern Matching: Conhecer essa feature foi um divisor de águas para mim. Em elixir, o match operator consiste somente em usar o =. Uma dica que eu consigo fornecer aqui é que ao invés de pensar como uma operação de atribuição (ou seja, isso recebe daquilo), pense em: isso tem um padrão igual ao daquilo? Se o padrão não bater, ocorre um MatchError.

Com isso, é possível verificar por padrões em variáveis simples, maps, tuples e lists. Vamos a dois exemplos básicos:

Para ilustrar do poder dessa feature, vamos a um exemplo de uma solução do problema FizzBuzz:

exemplo de fizzbuzz usando pattern matching

Esclarecendo algumas coisas que não foram mencionadas anteriormente: IO.inspect é uma função que fica escutando o resultado de uma outra função e escreve no console o que foi ouvido (isso é usado para debug), defp é a palavra reservada para criar uma função privada, e o operador _ é para avisar ao compilador que aquela variável não será usada (caso isso não seja feito, é emitido um warning informando essa declaração sem uso).

Lembre-se: checar por padrões usando funções e resultado de funções é possível também, ao invés de somente checar por padrões em variáveis e demais tipos simples. Agora, pense no poder de expressividade (e legibilidade!) de construções semelhantes em regras complexas usando essa feature.

Elixir is all about simplicity and extensibility ❤️

Até o próximo artigo!

--

--

Escritor-Desenvolvedor

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store