Alura > Cursos de Programação > Cursos de Elixir > Conteúdos de Elixir > Primeiras aulas do curso Elixir: Recursão, Enums e formas de execução

Elixir: Recursão, Enums e formas de execução

Mais sobre funções - Apresentação

Sejam muito bem-vindos à Alura. Eu sou o Vinicius Dias e vou guiar vocês nesse treinamento onde vamos criar uma aplicação com Elixir.

Já adquirimos certo conhecimento com Elixir, já sabemos um pouco da sintaxe básica, já aprendemos sobre pattern matching, bastante sobre recursão e até formas de execução do código Elixir.

Mas nesse treinamento vamos criar uma aplicação real. Inclusive eu tenho um gerenciador de tarefas da Erlang, rodando e mostrando os processos que vamos criar.

Teremos um agendador de tarefas que de minuto em minuto vai enviar uma mensagem para um outro processo, que é um servidor que criamos. Essa mensagem será recebida e um número aleatório será escrito no arquivo. Ou seja, de minuto em minuto o número vai mudar.

Claro que essa aplicação parece simples, mas para isso teremos que aprender bastante coisa interessante.

Começaremos falando do ecossistema do Elixir, como funciona se um processo morrer, como buscar dependências externas e como unir isso tudo.

Depois daremos um passo atrás e entenderemos o que é esse conceito de processos, que é diferente no sistema operacional e na Erlang, e, por consequência, no Elixir. Então falaremos bastante sobre processos, comunicação entre eles e abstrações.

Depois disso estaremos prontos para criar uma tarefa utilizando o Mix, que é uma ferramenta bastante interessante. A partir disso poderemos criar uma aplicação real. E essa aplicação vai gerenciar esses dois processos diferentes se comunicando.

No final de tudo conseguiremos executar testes. Então não só nós vamos executar os testes que já vêm por padrão, mas vamos entender o que é o tal do doctest, criar testes nossos até utilizando Mox e recursos da própria Erlang.

Então vale muito a pena acompanhar esse treinamento, e se em algum momento você tiver alguma dúvida, não hesite. Existe um fórum para alunos e você pode abrir sua dúvida lá.

Eu tento responder pessoalmente sempre que possível, mas quando eu não consigo, temos uma comunidade de alunos, de moderadores e instrutores muito solícita, e com certeza alguém vai conseguir te ajudar.

Agora chega de falar sobre o que veremos e vamos começar a ver. No próximo vídeo já começamos a entender um pouco sobre o ecossistema do Elixir.

Mais sobre funções - Guard clauses

Pessoal, bem-vindos de volta. Vamos falar um pouco mais sobre funções, rever alguns conceitos, ver novos conceitos e, claro, praticar. Aqui no nosso módulo de matemática, aquele arquivo "math.exs", eu quero adicionar uma nova função além do soma que já temos aqui. Eu vou adicionar uma função que verifica se o número que foi passado por parâmetro é 0 ou não.

Eu vou ter aqui uma função chamada zero?. Podemos utilizar esses caracteres em definições de funções, ponto de interrogação, ponto de exclamação. Não podemos usar qualquer carácter, mas esses dois pontos podemos. E aqui vou receber algum parâmetro, que vai ser o número, e dependendo do número que é passado ou retorno verdadeiro ou falso. def zero?(numero), do: true.

Vamos lá. Deixa eu apagar esse end porque eu não vou precisar, vou precisar de uma linha só. E se eu receber aqui o zero, ou seja, estou utilizando pattern matching para garantir que quando um parâmetro, cujo valor é 0, for recebido aqui, essa função que vai ser executada. Agora se eu receber qualquer outra coisa, ou seja, um parâmetro que eu nem preciso utilizar, então vou utilizar o underline, o underscore, vou retornar false. def zero?(0), do: true def zero? (_), do: false.

Temos aqui a função zero? definida. Se eu receber 0, eu vou retornar verdadeiro, caso contrário, eu vou retornar falso. Vamos ver lá no nosso iex, carregando esse módulo de matemática, o que temos de retorno. Quando eu executo MeuModulo.Math.zero?(0) é verdadeiro, com algum valor diferente de 0 é falso. Perfeito. Até aqui nada novo. Praticamos o pattern matching, praticamos também o uso de caracteres diferentes na definição de funções.

Só que tem um outro detalhe: se eu passar alguma coisa que nem é um inteiro aqui, eu não quero receber falso, eu quero um erro, quero que essa função só funcione para quando eu receber um inteiro. O que vou adicionar aqui? Antes do meu do:, ou seja, antes da implementação dessa minha função, antes do corpo dela, eu vou adicionar uma cláusula de guarda, ou seja, eu vou informar alguma coisa que vai ser uma verificação a mais para quando só o pattern matching não é o suficiente.

Aqui eu utilizei o pattern matching para pegar quando isso aqui é 0, mas e se eu quiser executar essa outra função aqui somente quando for um inteiro? Eu posso adicionar when. Esse when faz o quê? Ele realiza uma verificação para garantir que essa função vai ser executada ou não. Eu posso executar a função ou não dependendo de algumas condições.

Aqui eu vou precisar nomear essa variável porque eu vou verificar quando essa variável é inteira, ou seja, quando nosso X for um valor inteiro. Caso o nosso X não seja um valor inteiro, não seja 0, nem qualquer outro valor inteiro, nenhuma das funções zero? vai ser encontrada, então vamos ter um erro, a função não vai executar com sucesso. def zero?(0), do: true def zero?(x) whe(is)integer(x)), do: false.

Deixa eu recarregar o MeuModulo.Math, garantir que eu não escrevi nada errado e vamos lá. Quando eu chamo a função para 0, continua retornando true, quando eu chamo para algum diferente que seja diferente de 0, ele retorna false, e quando eu chamo para alguma coisa que não é um inteiro, temos um erro. Não encontramos essa função. Nenhuma função candidata foi encontrada.

Nenhuma cláusula de função está casando com os parâmetros que você passou e o parâmetro, no caso, foi uma lista. Quando você olha as nossas definições, temos uma cláusula, e é interessante conhecermos esse nome também, uma definição de função pode ser conhecida, pode ser chamada de cláusula e a mesma função pode ter várias cláusulas. Aqui eu tenho uma onde o parâmetro é 0 necessariamente, aqui eu tenho outra onde o parâmetro é qualquer coisa diferente de 0, porque se for 0 é essa aqui que vai ser executada.

Só que, além disso, além de ser qualquer coisa diferente de 0, precisa ser um inteiro, o seja, essa função aqui precisa retornar verdadeiro. Quando eu passo essa lista para função is_integer o que acontece? Ela vai me retornar falso, então essa função deixa de ser uma candidata viável para executar com esse parâmetro.

Podemos utilizar muita coisa aqui dentro das nossas cláusulas de guarda. Não podemos utilizar qualquer função, uma função que criarmos, por exemplo, só que podemos criar, podemos utilizar as funções que começam is_ porque isso aqui já são funções do próprio Elixir que são permitidas de utilizar aqui, podemos utilizar os operadores + e -, etc., enfim, bastante coisa pode ser utilizada e conforme a nossa necessidade vai aumentando, vamos experimentando novas funções, às vezes até operadores.

Essa é a ideia por trás de cláusulas de guarda, adicionamos verificações para saber se a função vai ser chamada ou não e, dessa forma, nós podemos ter definições diferentes para a mesma função baseado no parâmetro que foi passado além do que o pattern matching já fornece para nós.

Temos aqui uma nova funcionalidade de cláusula de guarda. Agora eu quero fazer uma coisa diferente. Ao invés de tratar com números, eu quero lidar um pouco mais com string. Eu quero criar uma função que junte duas strings e coloque um separador no meu. Por exemplo, se eu passar um espaço, vai ficar uma string espaço a outra. Só que eu quero ter um valor padrão para esse parâmetro de separador. Vamos falar de funções com valores padrão no próximo vídeo.

Mais sobre funções - Parâmetros padrão

Pessoal, bem-vindos de volta. Como eu comentei, quero criar uma função que una duas strings. Eu recebo uma string A e uma string B e algum separador, eu vou utilizar esse separador para ficar entre essas duas strings. Vamos criar aqui um arquivo que eu vou chamar de "concat.exs".

Eu vou criar aqui um módulo, então vou definir um módulo que vai ser "MeuModulo.Concat", de concatenação, que eu vou concatenar strings. Eu vou definir uma função chamada join, de unir. Eu vou receber uma string A, string B e um separador. def join(string_a, string_b, separador). O que eu quero fazer? Eu quero retornar a string A, unida com o separador, unida com a string B. string_a <> separador <> string_b.

É bastante simples. Vamos abrir o nosso iex e garantir que tudo continue funcionando, que essa função funcione. MeuModulo.Concat.join("Olá", "Mundo", " "), eu vou separar isso aqui com um espaço. Tenho lá "Olá Mundo" unidos com esse espaço. Agora o que eu quero fazer é exatamente fazer com que o separador tenha um valor padrão, que seja o espaço.

Eu não preciso informar esse último parâmetro aqui. Vamos fazer exatamente isso. Deixa eu fechar aqui o nosso terminal. Para eu adicionar um valor padrão, para eu informar: se nenhum valor for passado para separador, o valor padrão dessa variável vai ser espaço. Eu posso fazer isso através de \\ e coloco o valor padrão que eu quero, no meu caso o espaço. def join(string_a, string_b, separador \\ " ").

Deixa eu recompilar o MeuModulo.Concat, nenhum erro. Agora quando eu executar esse mesmo código sem o último parâmetro, mesmo que eu esteja passando somente dois parâmetros, ele vai encontrar essa função que tem três parâmetros, por quê? Porque o último é opcional, ele tem um valor padrão, então se eu não informar nada, esse vai ser o valor utilizado.

Tudo continua funcionando, mas se eu quiser passar algum outro separador, por exemplo o underline, também funciona. Agora um outro detalhe. Imagina que eu queira, assim como não quero passar esse último parâmetro, eu quero poder passar só o primeiro parâmetro, ou seja, se eu não passar esse segundo parâmetro, eu vou retornar direto somente a string A, a primeira string.

Hoje, isso ainda não funciona, então vamos implementar isso. Eu vou criar aqui uma nova função, def join(string_a, string_b, _separador), esse separador nem vou utilizar, então coloquei um underline na frente. Só que eu quero isso quando for nulo a minha string B. def join(string_a, string_b, _separador) when(is_nil(string_b)).

O que eu estou utilizando aqui? Estou utilizando uma cláusula de guarda e uma função is_nil, isso retorna se um valor é nulo ou não. "Mas Vinicius, esse valor não é nulo, o valor de string B simplesmente não vai existir, não é que é nulo", então vou transforma-lo também em um valor padrão aqui. def join(string_a, string_b, \\ nil, separador \\ " "). Só que aí entra um questionamento: será que eu preciso repetir essa declaração dos valores padrão nessa outra função também?

Vamos ver. Primeiro deixa eu retornar o que eu quero, que é somente a string A, ou seja, vamos recapitular o que eu fiz. Se eu passo os três parâmetros ou somente dois, eu vou executar essa função aqui e o separador pode ser um valor padrão, que vai ser o espaço, ou o que foi passado. Eu vou unir esses três parâmetros aqui, a string A, o separador e a string B.

Já se eu não passar o último parâmetro, o meu valor padrão é o espaço, então o separador vai ser o espaço. E se eu passar somente o primeiro parâmetro, eu quero retornar ele direto. Eu estou verificando se o segundo parâmetro for nulo, porque é o valor padrão que estou informando aqui, eu vou ignorar ele e retornar direto o primeiro parâmetro.

Vamos recompilar esse módulo e ver o que acontece. Aqui eu tenho um aviso, "a nossa cláusula de join com três parâmetros, não vamos conseguir fazer um match porque a anterior sempre vai casar". O que isso quer dizer? Vamos lá. Não existe nenhum caso onde vamos chegar nessa segunda aqui, só pela definição. Vamos precisar dessa nossa cláusula de guarda. Tudo bem, até aí ok. Vamos ver que tem um outro detalhe aqui.

Essa "join/3" tem múltiplas cláusulas de guarda, ou seja, tem muitas cláusulas, tem múltiplas definições dessa função, só que apesar de ter múltiplas definições, estamos definindo os valores padrão em um lugar separado, em lugares que o Elixir está se confundindo. Vamos simplificar aqui.

O que precisamos fazer? Eu preciso informar: Elixir, esses são os valores padrão desta função, independente de quantas definições, de quantas cláusulas eu tiver, os valores são esses. Como eu posso fazer isso? Eu posso utilizar algo que a galera de C deve se lembrar, que são os cabeçalhos de função. Eu vou copiar isso daqui e colocar aqui em cima, ou seja, nesse momento eu vou ter somente o cabeçalho, aqui eu não tenho o corpo da minha função.

Agora eu posso ter várias definições em si, vários corpos da minha função e eu não preciso informar os valores padrão, porque eles já foram informados aqui em cima. Só que pensa comigo: o Elixir vai avaliando de cima para baixo aquelas funções, então vimos aquele outro aviso, aquele outro warning, por quê? O Elixir nunca vai chegar nessa outra função aqui, porque essa cláusula de guarda nunca nem vai ser avaliada.

O que acontece? Se eu passar nulo ou não passar o segundo parâmetro, ainda assim ele vai cair aqui. O que eu preciso fazer nesse caso, para evitar aquele aviso, é trazer essa definição com a cláusula de guarda, como nosso when, vamos trazer ela para cima. Dessa forma, vamos ter a precedência correta.

O que isso quer dizer? Se eu passar somente o primeiro parâmetro significa que o segundo parâmetro vai ser nulo e quando esse parâmetro é nulo, eu posso executar essa função aqui. Ele vai parar de avaliar. Certo? Basicamente isso. Um detalhe para simplificarmos um pouco: esses parênteses são opções, eu não preciso adicionar esses parênteses na cláusula de when.

Continuando. Teoricamente, agora eu não vou ter mais avisos, então vou recompilar. Perfeito, sem aviso. Agora, vamos lá, MeuModulo.Concat.join, se eu colocar somente um valor, um valor só, isso aqui vai retornar esse próprio valor. Se eu passo um valor com outro, ele vai retornar unido com um espaço. E se eu passo o terceiro parâmetro, ele vai unir com esse separador que eu passei.

O que temos aqui de interessante? Nós podemos ter uma definição de função e isso é utilizado quando eu tenho valores padrão em mais de uma cláusula, ou seja, em mais de uma definição de função. Para você não precisar ficar se repetindo os valores padrão em todos os lugares e o Elixir também ter uma facilidade a mais de encontrar as funções, precisamos separar o cabeçalho da função, vamos falar como a galera de C e C++ entende, eu vou separar o cabeçalho da função da sua definição em si, do seu corpo.

Aqui nós temos o cabeçalho sendo definido com os valores padrão, só que o corpo de cada uma dessas cláusulas, de cada uma dessas funções, não precisa mais declarar de novo o valor padrão, isso já é implícito aqui. Se você reparar, nos últimos dois vídeos utilizamos algumas convenções interessantes. Eu falei que quando temos algumas funções ou macros que começam com is_alguma coisa, eu posso utilizar nas cláusulas de guarda.

Eu também falei que como eu não estou utilizando esse parâmetro aqui, eu posso começá-lo com underline, que o Elixir não vai me avisar que eu não estou utilizando esse parâmetro, nem nada do tipo. Eu também falei que aqui podemos utilizar pontos de interrogação na definição de uma função.

Existem vários detalhes de nomeação que, além de serem convenções, ou seja, além de serem boas práticas quando programamos em Elixir, também podem ter algum significado. Vamos entender um pouco dessas convenções de nomes que utilizamos no Elixir no próximo vídeo.

Sobre o curso Elixir: Recursão, Enums e formas de execução

O curso Elixir: Recursão, Enums e formas de execução possui 104 minutos de vídeos, em um total de 46 atividades. Gostou? Conheça nossos outros cursos de Elixir em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Elixir acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas