├── apps └── file ├── snippets ├── ping.ex ├── recursion.ex ├── quicksort.ex └── queue.ex ├── crono.md ├── pure_otp_exercise.md └── README.md /apps/file: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /snippets/ping.ex: -------------------------------------------------------------------------------- 1 | defmodule Ping do 2 | def start_link do 3 | spawn_link __MODULE__, :loop, [] 4 | end 5 | 6 | def loop() do 7 | receive do 8 | {:ping, sender} -> 9 | send sender, :pong 10 | _ -> 11 | :erlang.error(:i_was_expecting_ping) 12 | end 13 | end 14 | 15 | def ping(pid) do 16 | send pid, {:ping, self()} 17 | receive do 18 | :pong -> "Ok, everything is fine!" 19 | _ -> :erlang.error(:server_did_not_reply_with_pong) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /snippets/recursion.ex: -------------------------------------------------------------------------------- 1 | defmodule Recursion do 2 | def len([]) do 3 | 0 4 | end 5 | 6 | def len([h | t]) do 7 | """ 8 | len([1,2,3]) == 3 9 | """ 10 | 11 | 1 + len(t) 12 | end 13 | 14 | def sum([]) do 15 | 0 16 | end 17 | 18 | def sum([h | t]) when is_integer(h) do 19 | h + sum(t) 20 | end 21 | 22 | def map(f, []) do 23 | [] 24 | end 25 | 26 | def map(f, [x | xs]) do 27 | [f.(x) | map(f, xs)] 28 | end 29 | 30 | def squares(xs) do 31 | map &square/1, xs 32 | end 33 | 34 | def square(x) do 35 | x * x 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /snippets/quicksort.ex: -------------------------------------------------------------------------------- 1 | defmodule Quicksort do 2 | # Sorting the empty list and the singleton list is trivial 3 | def quicksort([]), do: [] 4 | def quicksort([x]), do: [x] 5 | 6 | # To sort a list, 7 | # we use the first element as "pivot", 8 | # then put all smaller elements to its left, 9 | # all all larger elements to its right, 10 | # and then sort each sides again until 11 | # we come to a trivial case [] or [x]. 12 | def quicksort([x|xs]) do 13 | smaller = Enum.filter(xs, fn y -> y < x end) 14 | larger = Enum.filter(xs, fn y -> y >= x end) 15 | quicksort(smaller) ++ [x] ++ quicksort(larger) 16 | end 17 | end -------------------------------------------------------------------------------- /crono.md: -------------------------------------------------------------------------------- 1 | **WIP** 2 | 3 | 1) Documentary 4 | 5 | - [ ] Watch http://doc.honeypot.io/elixir-documentary-2018/ 6 | 7 | 2) Getting started 8 | 9 | - [ ] Do all examples from 1 to 15 => https://elixir-lang.org/getting-started/introduction.html 10 | 11 | - [ ] Do the koans 1-15 => https://github.com/elixirkoans/elixir-koans 12 | 13 | 14 | 3) Pure Processes Exercices 15 | 16 | - [ ] Do the exercise: https://github.com/jaya/elixir-starter/blob/master/pure_otp_exercise.md 17 | 18 | 4) MIX & OTP 19 | 20 | - [ ] Do Examples from 1 to 4 => https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html 21 | - [ ] Do the koans 16-18 => https://github.com/elixirkoans/elixir-koans 22 | - [ ] [Optional] Watch: https://www.youtube.com/playlist?list=PLZZkJeUxu6QkFsqar5pIqaLoqVnBycDoU 23 | 24 | 5) OTP Exercices (WIP) 25 | 26 | - [ ] Do the exercise using OTP: https://github.com/jaya/elixir-starter/blob/master/pure_otp_exercise.md 27 | 28 | 6) Phoenix Course 29 | 30 | - [ ] Perform the course: https://www.udemy.com/the-complete-elixir-and-phoenix-bootcamp-and-tutorial/ 31 | 32 | 7) Final Project 33 | -------------------------------------------------------------------------------- /pure_otp_exercise.md: -------------------------------------------------------------------------------- 1 | Pure OTP Exercise 2 | 3 | Create a todo list using only pure processes 4 | 5 | 1) Add a todo to a todo-list 6 | 7 | ```ex 8 | TODO.add(%{ title: "study otp", completed: false }) 9 | => %{ id: "md5-943jg4938j39", title: "study otp", completed: false, created_at:"2018-10-02" } 10 | ``` 11 | 12 | 2) List todos 13 | 14 | ```ex 15 | TODO.list 16 | => [ %{ id: "md5-f0932jf934", title: "study otp", completed: false } ] 17 | ``` 18 | 19 | 3) Mark todo as completed 20 | 21 | ```ex 22 | TODO.complete("md5-943jg4938j39") 23 | => %{ id: "md5-943jg4938j39", title: "study otp", completed: true, created_at: "2018-10-03", completed_at:"2018-10-03" } 24 | ``` 25 | 26 | 4) Add validation to not allow duplicated todos titles 27 | 28 | ```ex 29 | TODO.add(%{ title: "study otp", completed: false }) 30 | => %{ id: "md5-943jg4938j39", title: "study otp", completed: false, created_at:"2018-10-02" } 31 | 32 | TODO.add(%{ title: "study otp", completed: false }) 33 | => %{ error: "task already created"} 34 | ``` 35 | 36 | 5) Add validation to not allow completion on already completed tasks 37 | 38 | ```ex 39 | TODO.complete("md5-943jg4938j39") 40 | => %{ id: "md5-943jg4938j39", title: "study otp", completed: true, created_at: "2018-10-03", completed_at:"2018-10-03" } 41 | 42 | TODO.complete("md5-943jg4938j39") 43 | => %{ error: "task already completed"} 44 | ``` 45 | 46 | 6) Add Supervision to the Application, when the todo process crashes it will be restarted automatically 47 | 48 | 7) Tests 49 | -------------------------------------------------------------------------------- /snippets/queue.ex: -------------------------------------------------------------------------------- 1 | defmodule Queue do 2 | defstruct [:front, :back, :count] 3 | 4 | def new() do 5 | %Queue{front: [], back: [], count: 0} 6 | end 7 | 8 | def enqueue(queue = %Queue{front: front, back: back, count: count}, new_elem) do 9 | # We always add items to the back list 10 | # the dequeue function will reverse the back list 11 | # and put into the front list when needed 12 | %Queue{queue | back: [new_elem | back], count: count + 1} 13 | end 14 | 15 | def dequeue(queue) do 16 | case queue do 17 | %{count: 0} -> 18 | # If the queue is empty there is nothing to return 19 | {:empty, queue} 20 | 21 | %{front: [], back: back} -> 22 | # If the front is empty, we swap it with the reversed back 23 | # and call dequee again -- it will fall back into the right case! 24 | dequeue(%Queue{queue | front: Enum.reverse(back), back: []}) 25 | 26 | %{front: [h | front], count: count} -> 27 | # If the front is not empty, take the first element as the 28 | # result and decrease the counter. 29 | {{:value, h}, %Queue{queue | front: front, count: count - 1}} 30 | end 31 | end 32 | 33 | # Pattern matching is enough here. 34 | def size(%Queue{count: count}) do 35 | count 36 | end 37 | 38 | # We can convert the queue to a list by 39 | # appending the reversed back to the front. 40 | def to_list(%Queue{front: front, back: back}) do 41 | front ++ Enum.reverse(back) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Curso de Elixir 2 | 3 | Na pasta `snippets` vamos colocando exercícios sobre a linguagem. 4 | 5 | Para rodar os snippets: 6 | 7 | `iex -r ` 8 | 9 | ## Talks 10 | 11 | Vamos colocar aqui uma lista de talks sobre Elixir, Phoenix e OTP. 12 | 13 | Tem alguns especialmente bons do Valim sobre Elixir, Ecto e Phoenix. 14 | 15 | ## Overview 16 | 17 | ### Elixir (Erlang) é uma linguagem com duas partes: a sequencial e a concorrente. 18 | 19 | A linguagem sequencial é usada para escrever código organizado em funções e módulos. 20 | 21 | A linguagem concorrente é usada para criar processos que executam o código sequencial e que se comunicam via mensagens. 22 | 23 | #### A linguagem sequencial 24 | 25 | A linguagem sequencial é funcional com tipos dinâmicos. 26 | 27 | As principais estruturas de dado são as tuplas (listas de tamanho fixo, geralmente 2-4 elementos), as lista ligadas (cada lista é uma cabeça com uma cauda) e os mapas (tabelas com chave e valor). 28 | 29 | Os valores são manipulados usando pattern-matching e funções que retornam uma cópia nova do valor. As variáveis nunca são alteradas, são copiadas e transformadas. 30 | 31 | O controle de fluxo é feito com IFs mas não há loops, é preciso usar recursão. Mas a recursão é muito fácil com pattern-matching. 32 | 33 | Não existem classes e objetos, apenas funções e módulos que agrupam essas funções para efeito de organização e compilação. 34 | 35 | Como não há mutação de valores, quando dois processos são chamados com o mesmo valor inicial, não precisamos nos preocupar com sincronização entre os processos. Cada processo tem uma cópia do valor original e quaisquer alterações criarão uma nova cópia separada. 36 | 37 | Material de estudo para pegar o básico de como escrever código bem simples: 38 | 39 | Para começar (são páginas pequenas): 40 | 41 | - https://elixir-lang.org/getting-started/introduction.html 42 | - https://elixir-lang.org/getting-started/basic-types.html 43 | - https://elixir-lang.org/getting-started/basic-operators.html 44 | - https://elixir-lang.org/getting-started/pattern-matching.html 45 | - https://elixir-lang.org/getting-started/case-cond-and-if.html 46 | - https://elixir-lang.org/getting-started/keywords-and-maps.html 47 | - https://elixir-lang.org/getting-started/modules-and-functions.html 48 | - https://elixir-lang.org/getting-started/try-catch-and-rescue.html 49 | - https://elixirschool.com/en/lessons/basics/basics/ 50 | - https://elixirschool.com/en/lessons/basics/collections/ 51 | - https://elixirschool.com/en/lessons/basics/pattern-matching/ 52 | - https://elixirschool.com/en/lessons/basics/control-structures/ 53 | - https://elixirschool.com/en/lessons/basics/pipe-operator/ 54 | - https://elixirschool.com/en/lessons/basics/modules/ 55 | - https://elixirschool.com/en/lessons/advanced/error-handling/ 56 | 57 | Se der tempo... 58 | 59 | - https://elixir-lang.org/getting-started/enumerables-and-streams.html 60 | - https://elixir-lang.org/getting-started/recursion.html 61 | - https://elixir-lang.org/getting-started/module-attributes.html 62 | - https://elixir-lang.org/getting-started/structs.html 63 | 64 | #### A linguagem concorrente 65 | 66 | Todo código roda em um processo, que é como um processo de sistema operacional, mas gerenciado pela máquina virtual do Elixir, e cada processo requer menos de 1kb de memória para ser criado e leva poucos microssegundos para rodar (processos de sistema operacional são muito mais pesados e lentos). 67 | 68 | Como os processos são muito leves, um sistema pode facilmente rodar com dezenas de milhares de processos. 69 | 70 | Os processos não tem estado no sentido comum. Quando iniciamos um processo nós rodamos uma função com alguns argumentos. O processo termina quando a função termina, e se quisermos um processo longo, nós fazemos uma recursão no final da própria função chamando ela de novo. Então o "estado" do processo fica no argumentos que vamos passando quando damos uma recursão na função do processo. 71 | 72 | Por exemplo, um pequeno servidor se parece com: 73 | 74 | ```elixir 75 | def loop(state) do 76 | receive do 77 | {:set, value} -> loop(value) 78 | {:get, sender} -> send sender, state; loop(state) 79 | other -> Logger.info("Couldn't understand; #{other}"); loop(state) 80 | end 81 | end 82 | ``` 83 | 84 | Outros processos não podem afetar `state`; eles podem mandar mensagens para o processo rodando `loop`, mas não há estado compartilhado. 85 | 86 | Para iniciar um processo que rode a função `loop` com estado inicial `0` basta: 87 | 88 | ```elixir 89 | spawn fn -> 90 | loop(0) 91 | end 92 | ``` 93 | 94 | Se usarmos `spawn_link`: 95 | 96 | ```elixir 97 | spawn_link fn -> 98 | loop(0) 99 | end 100 | ``` 101 | 102 | Então se o processo do `loop` travar, o processo que chamou `spawn_link` vai receber da VM uma mensagem avisando que o filho travaou. 103 | 104 | Nós geralmente não usamos as primitivas de `spawn` nem nos ocupamos muito com código concorrente quando escrevemos alguma serviço web. As bibliotecas de HTTP (como Plug) já cuidam da concorrência do servidor, gerenciando um pool de processos para cuidar dos requests. A parte do código que escrevemos em geral já está dentro de alguma abstração de concorrência. 105 | 106 | Como os processos não compartilham estado e a VM notifica falhas entre processos, é possível criar árvores de processos que reiniciam os filhos quando eles travam. Como esses processos são levíssimos, Erlang/Elixir são uma ótima linguagem para escrever servidores extremamente escaláveis e de alta disponibilidade. 107 | 108 | Por isso o comportamento comum em Erlang é deixar os processos travarem ao invés de escrever código extremamente defensivo. 109 | 110 | ### Garbage collection e concorrência/alta disponibilidade 111 | 112 | Como cada processo tem seu próprio espaço na memória, a coleta de lixo pode ser feita individualmente. Quando um processo passa por coleta de lixo, nenhum outro processo é pausado! A BEAM (a máquina virtual do Elixir) permite portanto coleta de lixo concorrente. 113 | 114 | Material de estudo: 115 | 116 | - https://elixir-lang.org/getting-started/processes.html 117 | - https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html 118 | - https://elixir-lang.org/getting-started/mix-otp/agent.html 119 | - https://elixir-lang.org/getting-started/mix-otp/genserver.html 120 | - https://elixir-lang.org/getting-started/mix-otp/supervisor-and-application.html 121 | - https://elixirschool.com/en/lessons/advanced/concurrency/ 122 | - https://elixirschool.com/en/lessons/advanced/otp-concurrency/ 123 | - https://elixirschool.com/en/lessons/advanced/otp-supervisors/ 124 | - https://elixirschool.com/en/lessons/advanced/behaviours/ 125 | 126 | ### Ferramentas 127 | 128 | O tooling para Elixir é bastante completo. 129 | 130 | #### IEx: REPL (read-eval-print-loop) para Elixir. 131 | 132 | - https://elixirschool.com/en/lessons/basics/iex-helpers 133 | - https://elixir-lang.org/getting-started/debugging.html 134 | 135 | #### Mix: Projetos/build 136 | 137 | - https://elixirschool.com/en/lessons/basics/mix/ 138 | - https://elixirschool.com/en/lessons/basics/mix-tasks/ 139 | 140 | #### Hex: Package manager 141 | 142 | #### ExUnit: Framework de testes unitários 143 | 144 | - https://elixirschool.com/en/lessons/basics/testing/ 145 | 146 | #### ExDoc: Framework para gerar documentação 147 | 148 | - https://elixirschool.com/en/lessons/basics/documentation/ 149 | 150 | ### Material de estudo: 151 | 152 | #### Bibliotecas importantes 153 | 154 | Existem algumas bibliotecas de uso comum em Elixir para projetos web. 155 | 156 | ##### Plug 157 | 158 | Plug é uma biblioteca para escrever servidores de HTTP. Os "plugs" são juntados num pipeline que processa um request. Por exemplo um plug faz o parsing do corpo do request, outro valida parâmetros, outro faz a lógica de negócio, outro roteia a request para módulos específicos. 159 | 160 | No final do pipeline, um plug simplesmente retorna um objeto com a resposta que deve ser enviada ao cliente. 161 | 162 | O plug faz apenas a lógica para cima do HTTP; o HTTP em si é gerenciado por uma biblioteca que não usamos diretamente, a Cowboy, que por sua vez roda por cima do Ranch, que gerencia recursos de rede junto ao sistema operacional. 163 | 164 | - https://elixirschool.com/en/lessons/specifics/plug/ 165 | - https://github.com/elixir-plug/plug 166 | 167 | ##### Ecto 168 | 169 | Ecto é o ORM do Elixir. Com o Ecto podemos escrever migrações, definir modelos sobre tabelas de bancos de dados, e realizar consultas e alterações nos bancos. Mas o Plug não trabalha com ActiveRecords como Rails. Ele trabalha com o Repository Pattern, mais comum em Java e .NET. 170 | 171 | - https://elixirschool.com/en/lessons/specifics/ecto/ 172 | 173 | ##### Phoenix 174 | 175 | Phoenix é um framework completo para programar web em Elixir. Ele roda usando Plugs, mas traz o tipo de conveniência do Rails: controllers, autenticação, cache, session management, views, live code reloading, etc. Sobre usar Plug ou Phoenix depende muito do tipo de aplicação. O Plug é suficiente para muita coisa. 176 | 177 | ## Um projeto para aprender 178 | 179 | O projeto é criar um servidor de to-do com uma interface HTTP usando Plug, persistência em SQL usando Ecto, e rodando sob uma árvore de supervisão. Os to-dos devem ser distribuídos entre N servidores. Na prática o SQL será um gargalo, mas o exercício serve do mesmo jeito. 180 | 181 | ## FAQ 182 | 183 | ### O que é OTP? 184 | 185 | OTP é Open Telecom Platform. É um conjunto de biliotecas (módulos) e abstrações para escrever aplicações distribuídas. Por exemplo, o GenServer é uma abstração de um servidor com um loop principal que pode receber mensagens assíncronas (_casts_), receber e responder mensagens síncronas (_calls_), ser adicionado a uma árvore de supervisão. O GenServer inclui também um lógica para lidar com timeouts. 186 | 187 | ### Por quê usamos listas e não arrays? Qual a diferença? 188 | 189 | Listas são compostas de um primeiro elemento (head) e outra lista (tail): 190 | 191 | ```elixir 192 | [1, 2, 3] == [1|[2|[3]]] 193 | ``` 194 | 195 | Em em termos de estruturas de dados (& é um ponteiro, informalmente): 196 | 197 | ```elixir 198 | X1 = (1, &X2) 199 | X2 = (2, &X3) 200 | X3 = (3, NULL) 201 | ``` 202 | 203 | Arrays tem tamanho fixo e (tipicamente) correspondem a uma região contígua de memória. 204 | 205 | O acesso em arrays é mais rápido, basta usar um offset para calcular onde na memória está um elemento. 206 | 207 | Em listas precisamos sempre percorrer os ponteiros. 208 | 209 | O benefício de listas é que podem mudar de tamanho, representam muito facilmente listas e filas, e suportam pattern-matching. O compilador não consegue fazer pattern-matching em arrays sem saber o tamanho delas de antemão. Além disso, trabalhar recursivamente com listas é muito fácil: 210 | 211 | ```elixir 212 | def sum([]), do: 0 213 | def sum([x|xs]) do 214 | x + sum(xs) 215 | end 216 | ``` 217 | 218 | Arrays são mais adequadas para computação numérica, mas em Elixir/Erlang sempre estamos mais interessado em performance em redes e I/O em geral. Como é fácil ler o primeiro elemento de listas, não perdemos performance por usá-las. 219 | --------------------------------------------------------------------------------