Aprendizado Elixir - módulo 2
Published: 2022-06-27 13:00:57

Nesse módulo, criaremos um projeto chamado reports_generator, então já vamos cria-lo no terminal: 

```
mix new reports_generator


* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/reports_generator.ex
* creating test
* creating test/test_helper.exs
* creating test/reports_generator_test.exs
```

Desta vez, já iremos utilizar uma lib externa, então iremos aprender a instalar uma lib externa. 

A lib em questão será o `CREDO`.

Para instala-lo, basta pesquisar por “elixir credo” no google, ou acessar o [link](https://github.com/rrrene/credo).


**Mas afinal, o que é o credo?**

O credo é um analisador sintático de código. Ele Irá nos ajudar a manter boas práticas de escrita do nosso código, além do mix format que irá nos ajudar a formatar o código. 

Para instalar o credo, basta acessar a pasta “mix .exs” dentro do projeto e ir até a função “defp deps do”.
Lá iremos inserir: 

> {:credo, "~> 1.6", only: [:dev, :test], runtime: false}

No terminal, utilizamos o comando “mix deps.get” para instalar as libs.

Agora, uma vez que o credo está instalado, rodaremos o comando: 

```
mix credo gen.config
```
Este comando irá gerar um arquivo de configuração  do credo que podemos configura-lo para decidirmos o que queremos que ele cheque ou não.

Agora, se visualizarmos dentro do nosso código, veremos que foi criada uma pasta com o nome “.credo.exs":

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6kiw04mh9jw2n71891g2.png)
 
Para testarmos, vamos rodar o comando `mix credo` no terminal

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z2hmjucksx98re0se502.png)


Como estamos em um projeto recém criado, ele não irá apresentar nenhuma issues. 
Porém, se entrarmos dentro da lib “reports_generator.ex” e apagarmos a doc e rodarmos novamente o comando mix credo, ele irá nos devolver a seguinte mensagem: 

```
3 mods/funs, found 1 code readability issue.
```

 Agora, ele nos diz que os módulos devem ter documentação. 
Como não iremos trabalhar com documentação, iremos a pasta credo.exs e adicionar “false” em um dos nossos checks:

`{Credo.Check.Readability.ModuleDoc, false},`

Outra coisa que podemos fazer é observar que o credo vem como padrão um limite por linhas (nesse caso, 120)
Para obtermos esse resultado no credo, precisamos rodar um comando mais severo do credo:

```
 mix credo —strict  
```

E o credo irá nos retornar:

```
Line is too long (max is 120, was 164).
```

> Existe uma extensão no vscode chamada Elixirlint que executa de forma dinâmica e executa os erros de forma ao vivo, sem precisarmos de rodar os checks o tempo todo. 


Pra darmos continuidade ao nosso projeto, utilizaremos um arquivo que irá conter todos os CSV`s que iremos precisar. 

Vamos alterar o nome da função para def build(filename) que irá receber o nome do arquivo que queremos abrir
Para ler o arquivo, colocamos dentro da nossa função:

> File = File.read(“reports/#{filename})”)

Agora, vamos rodar o `iex -S mix` para  tentar abrir um arquivo do nosso projeto

```
iex(1)> ReportsGenerator.build("report_test.csv")
{:ok,
 "1,pizza,48\r\n2,açaí,45\r\n3,hambúrguer,31\r\n4,esfirra,42\r\n5,hambúrguer,49\r\n6,esfirra,18\r\n7,pizza,27\r\n8,esfirra,25\r\n9,churrasco,24\r\n10,churrasco,36"}
```

E obtemos um `:ok`. 
Mas como podemos lidar melhor com o erro ao não encontrarmos um arquivo?
Na prática, criamos o “corpo” do `case` desta maneira:


> Nós tentamos dar match em alguma ocasião , ou seja,  se tivermos um “ok, result”, devolveremos result.
Mas, se recebermos um “error, reason”, devolveremos um reason (o motivo de error)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsh3kg172eebahbpyuin.png)


Agora, testamos no terminal as duas opções e ele irá nos retornar:

```
iex(4)> ReportsGenerator.build("report_test.csv")
"1,pizza,48\r\n2,açaí,45\r\n3,hambúrguer,31\r\n4,esfirra,42\r\n5,hambúrguer,49\r\n6,esfirra,18\r\n7,pizza,27\r\n8,esfirra,25\r\n9,churrasco,24\r\n10,churrasco,36"
iex(5)> ReportsGenerator.build("report_tawdest.csv")
:enoent
```
 

Uma outra forma, talvez mais elegante e mais aderente ao nosso exemplo quando entendermos o nosso módulo. 

Vamos iniciar o conhecimento em **Pipe Operator**

Vamos dar um exemplo na prática para entendermos o seu funcionamento. 
No terminal, vou criar uma String simples:

`iex(4)> string = " aaaaaaaaAaaaAa \n"
" aaaaaaaaAaaaAa \n"`

Agora, queremos gerar uma string sem o “/n” e sem esses “a” maiúsculos. 
Uma opção seria passar: String.trim(string) 
Assim, ele deletaria todos os espaços e o “/n”. 
Só que estamos em uma linguagem imutável, então teríamos que atribuir a nossa string novamente: 

```
String = String.trim(string)
```

E só assim modificamos a nossa string de fato. 
Mas e se eu quiser deixa-la toda minúscula também?
Então teríamos que de fato fazer outra atribuição: 

```
iex(10)> string = " aaaaAaaaaaa \n" 
" aaaaAaaaaaa \n"
iex(11)> String.trim(string)
"aaaaAaaaaaa"
iex(12)> string = String.trim(string)
"aaaaAaaaaaa"
iex(13)> string
"aaaaAaaaaaa"
iex(14)> string = String.downcase(string)
"aaaaaaaaaaa"
```

Pouco legível e muita concatenação. Atrapalharia inclusive a leitura do código por outros desenvolvedores. 

E onde o Pipe Operator entra? 
Vamos seguir o mesmo exemplo de cima:

```
iex(16)> " aaaaaaAAAAAAaaaa \n" |> String.trim() |> String.downcase()
"aaaaaaaaaaaaaaaa"
```

Olha como fica legal, mais legível e extende bem menos o código. 

O Pipe Operator nada mais faz do que pegar o primeiro o resultado de qualquer operação antes dele e passar pra função seguinte como primeiro argumento, e o resultado da da primeira função ele passaria pra segunda, e assim adiante, e por isso não há nada dentro dos parênteses, pois está vindo como resultado do “|>(pipe).

E como podemos aplicar isso dentro do nosso exemplo lá no projeto?

Vamos lá no código pegar o nome do nosso arquivo “filename” e concatenar com a variável reports:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3t4urf91x2ti0mgnvwt.png)
 

Estamos pegando essa string: "reports/#{filename}" e passando para a primeira função |>File.read() e o resultado da função file.read eu estou passando para a função |>handle_file

Agora, vamos testar com um arquivo que existe: 

```
iex(2)> ReportsGenerator.build("report_test.csv")
"1,pizza,48\r\n2,açaí,45\r\n3,hambúrguer,31\r\n4,esfirra,42\r\n5,hambúrguer,49\r\n6,esfirra,18\r\n7,pizza,27\r\n8,esfirra,25\r\n9,churrasco,24\r\n10,churrasco,36"
```

E quando tentamos um arquivo não existente, obtemos: 

```
iex(3)> ReportsGenerator.build("report_tesawdt.csv")
"Error while opening file!"
```

Assim, conseguimos trabalhar com Pipe Operator e pattern matching, controlando o fluxo da nossa execução, sem dependermos de if&else ou “complicando” o nosso código.

Voltando ao nosso projeto, dentro temos um arquivo chamado “report_complete.csv” contendo 300 mil linhas.
Será com esse arquivo que iremos inicialmente trabalhar.

Em um cenário real, um arquivo contendo 1 milhão de linhas poderia ser muito pesado de ser carregado completamente na memória. 
Então,  a primeira coisa que iremos mudar é ao invés de utilizarmos “File.read()” nós iremos usar “File.stream!().

Usaremos um IO.inspect() para debugar no terminal o resultado desta operação. O IO sempre irá imprimir no terminal aquilo que passamos para ele como parâmetro. 

Agora, faremos um recompile no terminal e iremos chamar novamente a função `ReportsGenerator.build("report_test.csv”)`, e ele irá nos retornar: 

```

%File.Stream{
  line_or_bytes: :line,
  modes: [:raw, :read_ahead, :binary],
  path: "reports/report_test.csv",
  raw: true
}
%File.Stream{
  line_or_bytes: :line,
  modes: [:raw, :read_ahead, :binary],
  path: "reports/report_test.csv",
  raw: true
}
```

Assim, ele nos traz uma Struct (nada mais é do que um map com um nome, resumidamente por hora)
O file.stream não traz o conteúdo em si, mas sim metadados

O file.stream possui um “!” no fim pois ele só abre arquivos existentes, não devolvendo tuplas com “error” ou “ok”.

Vamos usar o Enum.each e para cada elemento que tiver nesse arquivo (cada linha), daremos um IO.inspect

>  |> Enum.each(fn elem -> IO.inspect(elem) end)

E agora vamos chamar o mesmo comando `iex(14)> ReportsGenerator.build("report_test.csv”)`
E ele irá nos retornar: 

```
%File.Stream{
  line_or_bytes: :line,
  modes: [:raw, :read_ahead, :binary],
  path: "reports/report_test.csv",
  raw: true
}
"1,pizza,48\n"
"2,açaí,45\n"
"3,hambúrguer,31\n"
"4,esfirra,42\n"
"5,hambúrguer,49\n"
"6,esfirra,18\n"
"7,pizza,27\n"
"8,esfirra,25\n"
"9,churrasco,24\n"
"10,churrasco,36"
:ok

```

Agora, pra cada linha do arquivo, ele leu e deu inspect.

Agora, faremos um exemplo utilizando Enum.map e para cada linha nós iremos trabalhar essa linha. 
Por exemplo: nós iremos separar os elementos para visualizarmos de maneira melhor cada item. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zr94abpvgk5afa3uhu1e.png)
 

Adicionamos algumas strings e mudamos o nosso enum e quando testamos novamente no terminal, ele nos retorna desta forma: 

```
iex(22)> ReportsGenerator.build("report_test.csv")
[
  ["1", "pizza", "48"],
  ["2", "açaí", "45"],
  ["3", "hambúrguer", "31"],
  ["4", "esfirra", "42"],
  ["5", "hambúrguer", "49"],
  ["6", "esfirra", "18"],
  ["7", "pizza", "27"],
  ["8", "esfirra", "25"],
  ["9", "churrasco", "24"],
  ["10", "churrasco", "36"]
]
```

Mas por que utilizamos o &?
Quando criamos uma função anonima, podemos usar o fn com o E comercial  e criar uma função de forma implícita.

Prosseguindo com o nosso parseamento do arquivo, podemos ver que ainda não está em um formato muito legal.
Estamos consumindo uma lista de listas basicamente, o que não seria muito legal. 
Pra melhorarmos isso podemos utilizar a função `reduce`, que funciona da seguinte forma: 

Enum.reduce([1,2,3,4], 0, fn elem, acc -> acc + elem end)


Nessa função, passamos a coleção de dados que queremos passar, no nosso caso uma lista “1, 2,3.4”, um valor de acumulador inicial, no nosso caso “0”,  e uma função anonima “fn”, e essa função anonima sempre como primeiro elemento ela vai receber cada elemento da lista, e como segundo elemento o valor atual do acumulador, no nosso caso “acc"



Quando aplicamos a função dentro do nosso projeto, obtemos como resultado: 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2m5x0xzauyr946iwbpk3.png)
 

iex(4)> ReportsGenerator.build("report_test.csv")
%{
  "1" => 48,
  "10" => 36,
  "2" => 45,
  "3" => 31,
  "4" => 42,
  "5" => 49,
  "6" => 18,
  "7" => 27,
  "8" => 25,
  "9" => 24

Agora, ao invés de uma lista de listas, temos um Map onde temos o ID do usuário e quanto ele consumiu, de acordo com o nosso arquivo de teste.