Neste capítulo abordaremos funções em Elixir.
Uma função recebe zero ou mais valores como entrada e retorna um valor como saída. As entradas e saídas podem ser de todos os tipos de Elixir (inclusive dos que não mostraremos aqui).
Elixir já vem com várias funções. Estas funções fazem parte de módulos. Por exemplo, o módulo 'String' contém a função 'upcase', com aridade 2 (portanto, nos referimos a ela como 'String.upcase/2'), que converte todos os caracteres em uma cadeia de caracteres para letras maiúsculas.
String.upcase("não grite, por favor!")
Você pode ter achado estranho a aplicação de String.upcase/2 acima receber apenas um argumento. Isto acontece pois Elixir permite que sejam definidas funções com argumentos default. Neste caso, o segundo argumento é o modo. Se você não passar nada, é o mesmo que chamar da seguinte forma:
String.upcase("não grite, por favor!", :default)
Mas eu posso escolher entre quatro valores: :default, :ascii, :greek, :turkic. Vamos testar dois deles e observar a diferença.
# observe o "ã"
String.upcase("não grite, por favor!", :ascii)
# observe o I acentuado
String.upcase("não grite, por favor!", :turkic)
Você pode definir seu próprio módulo usando 'defmodule'. E suas funções usando 'def'. Veja a sintaxe nos exemplos abaixo:
defmodule Matematica do
def soma(num1, num2) do
num1 + num2
end
def subtracao(num1, num2) do
num1 - num2
end
end
Observe que o formato é:
defmodule <NOME_DO_MODULO> do
def <NOME_DA_FUNCAO>(<LISTA_DE_ARGUMENTOS>) do
<CORPO_DA_FUNCAO>
end
end
Depois que você define o módulo e o avalia, você pode usar suas funções.
Matematica.soma(30, 40)
Matematica.subtracao(30, 56)
Vamos definir outro módulo:
Enum.sum([1.0, 3, 4])
Enum.count([1, 34, 56, 78, 1])
defmodule Turma do
def media(lista_notas) do
Enum.sum(lista_notas) / Enum.count(lista_notas)
end
def aprovado?(nota) when nota >= 6 do
true
end
def aprovado?(_), do: false
end
No exemplo acima, usamos duas funções do módulo Enum: uma para somar todos os valores de uma lista de notas (Enum.sum/1) e outra para contar quantas notas tem na lista (Enum.count/1). A partir disto, calculamos a média de todas as notas da turma.
Em 'Turma.aprovado?/1' usamos a convenção de terminar o nome da função que sempre retorna um valor lógico com '?'.
Na linha 6 'def aprovado?(nota) when nota>=6 do', usamos uma guarda, que é introduzida com 'when' logo após a lista de parâmetros. Não temos tempo aqui de entrar em todos os detalhes de guardas. Neste caso, estamos dizendo que se a nota for maior ou igual 6, 'true' será retornado. Caso contrário (observe o casamento de padrões em ação na linha 9 com o uso do '_'), 'false' será retornado. Ainda na linha 9, observe que em vez de:
def <NOME_DA_FUNCAO> <GUARDA_OPCIONAL> do
<RESULTADO>
end
podemos escrever
def <NOME_DA_FUNCAO> <GUARDA_OPCIONAL>, do: <RESULTADO>
Podemos ter várias cláusulas (ou seja, vários 'def's) para uma mesma função. A primeira que se aplicar, ou seja, cujo padrão casar será usada.
Turma.media([10, 4, 6, 7, 8])
Turma.aprovado?(6)
Turma.aprovado?(5)
Turma.aprovado?(7)
Agora que você já sabe o que são funções, vamos falar pra você de um tipo de função que pode ser muito útil em alguns casos: as funções sem nome (também chamadas de "funções anônimas").
Funções anônimas são funções que, como o próprio nome diz, não têm um nome. Elas são descritas apenas definindo qual sua entrada (se alguma) e os comandos que vão produzir sua saída.
No exemplo abaixo, definimos uma função que recebe um valor 'num' (que representa um número) e retorna este número adicionado de 1.
fn num -> num + 1 end
Quando você avalia o código acima, você percebe que Elixir te dá algo como:
#Function<42.125776118/1 in :erl_eval.expr/6>
Este valor, por si só, não tem muita utilidade para nós.
Como então podemos aplicar a função anônima que acabamos de definir a um valor, como 2? Normalmente não fazemos isso em Elixir, mas uma solução é atribuir a função a uma variável.
f = fn num -> num + 1 end
E só depois chamar a função:
f.(2)
Perceberam que, para chamar a função 'f', precisamos colocar um ponto antes dos parênteses? É uma característica de Ellxir (que na prática não é um problema).
Outra forma é fazer a chamada diretamente (também usando o ponto):
(fn num -> num + 1 end).(2)
O uso realmente interessante de funções anônimas é em funções de ordem mais alta (higher-order functions). Funções de ordem mais alta recebem uma ou mais funções como argumento, ou retornam uma função.
O módulo Enum inclui várias funções de ordem mais alta.
Por exemplo, a função 'Enum.map/2' recebe, no exemplo abaixo, como entrada uma lista '[10, 20, 3, 45]' e uma função anônima de aridade 1 que soma 12 ao valor que receber:
Enum.map([10, 20, 3, 45], fn x -> x + 12 end)
A função 'Enum.reduce/3' recebe como argumento uma lista, um valor inicial e uma função de aridade 2. No exemplo abaixo, a lista é '[1, 2,3]', o valor inicial é 0 e a função soma um elemento da lista ao acumulador, que passa a ter o valor desta soma. Deste modo, o resultado será '0+1+2+3'.
Enum.reduce([1, 2, 3], 0, fn elemento, acumulador -> elemento + acumulador end)
E como seria um exemplo de uma função que retorna outra função? No módulo abaixo nós definimos uma função 'HOF.incrementador/1', que recebe como entrada um número e retorna uma função de aridade 1 que incrementa um outro valor a este número.
defmodule HOF do
def incrementador(numero) do
fn valor -> valor + numero end
end
end
Ou seja, se eu chamar desta forma:
HOF.incrementador(3)
Eu obtenho uma função que incrementa 3 ao valor que receber. Portanto, se eu passar 5 a esta função, o resultado será 8.
HOF.incrementador(3).(5)
É útil? Não lembro de nenhuma utilidade deste tipo de função em meu código. Mas pode ser útil em bibliotecas bem específicas.
O operador pipe (representado pelos dois símbolos '|>') é uma forma de chamar funções que permite que as chamadas sejam encadeadas de uma forma elegante e prática.
Por exemplo, em vez de fazer:
require Integer
Enum.all?(Enum.map([1, 2, 3, 4], fn x -> x * 2 end), &Integer.is_even/1)
Podemos escrever:
require Integer
[1, 2, 3, 4]
|> Enum.map(fn x -> x * 2 end)
|> Enum.all?(&Integer.is_even/1)
Ou seja, o pipe envia o resultado de uma expressão como o primeiro parâmetro para a próxima chamada a função.
Uma funcionalidade que pode ser útil é a definição de funções privadas em módulos.
Nós vimos anteriormente como definir uma função em um módulo:
defmodule Mat do
def media(num1, num2) do
(num1 + num2) / 2
end
end
Vamos supor que eu queira ter uma função auxiliar, uma função que é usada apenas pelas outras funções do módulo. Este é o caso em que definimos uma função previada e para isso, em vez de 'def', usamos 'defp'.
defmodule Matem do
def media(num1, num2) do
(num1 + num2) / 2
end
def fatorial(num) when num > 0 do
fatorial(num, 1)
end
def fatorial(_num) do
:error
end
defp fatorial(0, fatorial_atual) do
fatorial_atual
end
defp fatorial(num, fatorial_atual) when num > 0 do
fatorial(num - 1, fatorial_atual * num)
end
end
Matem.fatorial(1000)
Você lembra do exercício em que você fez no primeiro livebook? Percebeu que nesse capítulo implementamos ela utilizando o módulo Enum
?
defmodule Turma do
def media(lista_notas) do
Enum.sum(lista_notas) / Enum.count(lista_notas)
end
def aprovado?(nota) when nota >= 6 do
true
end
def aprovado?(_), do: false
end
Elixir tem diversos módulos que possuem funções que podem nos auxiliar a simplificar o desenvolvimento de nosso código, como vimos acima no uso de Enum
e no inicio desse livebook com String
.
- Leia essas documentações de forma rápida por cima e busque nelas informações quando for resolver esse exercício: Enum, String e Map.
E então resolva esses problemas:
- Retorne o tamanho da String "pneumoultramicroscopicossilicovulcanoconiótico".
- Retorne uma Lista que possui os mesmos items da lista [:a, :b, :c] porém com os itens na ordem contrária, ou seja [:c, :b, :a].
- Retorne um novo mapa que é a união desses dois mapas:
primeiro_mapa = %{ :um => 1, :dois => 2 }
segundo_mapa = %{ :tres => 3, :quatro => 4 }
- Retorne uma lista de palavras da sentença:
# execute essa célula de código
sentenca = """
Como de hábito, Policarpo Quaresma, mais conhecido por Major Quaresma, bateu em casa às quatro e quinze da tarde. Havia mais de vinte anos que isso acontecia. Saindo do Arsenal de Guerra, onde era subsecretário, bongava pelas confeitarias algumas frutas, comprava um queijo, às vezes, e sempre o pão da padaria francesa.
"""
- Crie uma nova lista onde cada elemento corresponde a um elemento da lista abaixo multiplicado por 2. Depois disso, some todos os elementos da nova lista.
# execute essa célula de código
lista_de_numeros = [4, 8, 12, 16, 20, 24, 28, 32, 36]
O departamento de Matemática da Universidade Técnica Federal do Paraná entrou em contato com você. Eles querem usar Elixir para trabalhar com Álgebra Linear e para isso precisam que você implemente um módulo em Elixir para que eles possam usar para fazer operações com Matrizes que eles precisam. Para eles é importante:
- Soma de Matrizes
- Subtração de Matrizes
- Multiplicação de Matrizes
- Multiplicação de Matriz por Escalar (chamado de Produto Escalar)
- Determinante da Matriz (para Matrizes até 3x3)
Para a soma, subtração e multiplicação de matrizes, as matrizes inseridas poderão ter quaisquer dimensões (verifique se a operação é factível antes de executá-la).
Uma matriz é representada por Listas dentro de Listas em Elixir.
matriz = [[0,0,1], [0,1,0], [1,0,0]]
Você pode utilizar qualquer recurso ou técnica, incluindo implementar funções auxiliares, features que ainda não vimos até aqui ou qualquer outra coisa que julgar necessário. Porém, com o que viu até aqui deve ser o suficiente para que possa implementar essa solução.
Abaixo, temos um "esqueleto" da implementação. Os professores esperam que essa "API" (o nome do módulo, das funções e suas assinaturas) sejam respeitadas, pois não vão investigar a implementação, mas esperam usar chamando-as com esses nomes e assinaturas.
defmodule AlgebraLinear do
def soma_matrizes(matriz_a, matriz_b) do
# A fazer
end
def subtrai_matrizes(matriz_a, matriz_b) do
# A fazer
end
def multiplica_matrizes(matriz_a, matriz_b) do
# A fazer
end
def escalar(matriz, escalar) do
# A fazer
end
def determinante(matriz) do
# A fazer
end
end
Você é um desenvolvedor web que trabalha em uma empresa que oferece serviços de streaming de música. Você está usando Elixir para criar uma API que permite aos usuários pesquisar, ouvir e avaliar as músicas disponíveis na plataforma. Você recebeu uma tarefa de implementar uma função que recebe uma lista de músicas e retorna uma lista de recomendações baseadas nas preferências do usuário.
Uma música é representada por um mapa com as seguintes chaves:
- :titulo - uma string com o nome da música
- :artista - uma string com o nome do artista
- :genero - uma string com o gênero musical
- :duracao - um inteiro com a duração da música em segundos
- :avaliacao - um número de 0 a 5 com a avaliação média da música pelos usuários
musica = %{
titulo: "Starway to Heaven",
artista: "Led Zeppelin",
genero: "Rock",
duracao: 482,
avaliacao: 4.9
}
Uma preferência do usuário é representada por um mapa com as seguintes chaves:
- :genero - uma string com o gênero musical preferido pelo usuário
- :duracao_min - um inteiro com a duração mínima das músicas que o usuário gosta em segundos
- :duracao_max - um inteiro com a duração máxima das músicas que o usuário gosta em segundos
- :avaliacao_min - um número de 0 a 5 com a avaliação mínima das músicas que o usuário quer ouvir
preferencia = %{
genero: "Rock",
duracao_min: 200,
duracao_max: 400,
avaliacao_min: 4.5
}
A função de recomendação deve receber uma lista de músicas e uma preferência do usuário e retornar uma lista de músicas que atendem aos critérios da preferência, ordenadas por avaliação em ordem decrescente. Se houver mais de uma música com a mesma avaliação, a ordem deve ser alfabética pelo título.
Você deve usar funções anônimas e as funções map, filter e reduce para implementar essa função. Você pode usar qualquer recurso ou técnica que julgar necessário, mas com o que viu até aqui deve ser o suficiente para que possa implementar essa solução.
defmodule Recomendacao do
def recomenda(musicas, preferencia) do
# A fazer
end
end
# Define uma lista de músicas
musicas = [
%{titulo: "Hotel California", artista: "Eagles", genero: "Rock", duracao: 391, avaliacao: 4.7},
%{titulo: "Rolling in the Deep", artista: "Adele", genero: "Pop", duracao: 228, avaliacao: 4.5},
%{titulo: "Bohemian Rhapsody", artista: "Queen", genero: "Rock", duracao: 355, avaliacao: 4.9},
%{
titulo: "Billie Jean",
artista: "Michael Jackson",
genero: "Pop",
duracao: 292,
avaliacao: 4.4
},
%{titulo: "Yesterday", artista: "The Beatles", genero: "Rock", duracao: 138, avaliacao: 4.6},
%{titulo: "Shape of You", artista: "Ed Sheeran", genero: "Pop", duracao: 233, avaliacao: 4.1},
%{
titulo: "No Woman, No Cry",
artista: "Bob Marley",
genero: "Reggae",
duracao: 245,
avaliacao: 4.8
},
%{
titulo: "Redemption Song",
artista: "Bob Marley",
genero: "Reggae",
duracao: 179,
avaliacao: 4.7
}
]
# Define uma preferência do usuário
preferencia = %{genero: "Pop", duracao_min: 200, duracao_max: 250, avaliacao_min: 4.5}
# Chama a função de recomendação com a lista de músicas e a preferência do usuário
Recomendacao.recomenda(musicas, preferencia)
Você está participando de um emocionante concurso de escultura de círculos, onde artistas de todo o mundo exibem suas obras-primas circulares. Cada participante contribui com uma lista de raios de círculos usados em suas esculturas. O júri está ansioso para premiar as melhores esculturas, e você quer dar um toque de tecnologia à sua apresentação.
Você decide criar um programa em Elixir para ajudar a analisar e apresentar os dados dos raios dos círculos de forma a impressionar os juízes. Seu programa será responsável por calcular várias estatísticas importantes dos raios, o que pode destacar a singularidade de sua escultura.
-
Encontrar o raio mínimo e máximo para mostrar o quão variados são os tamanhos dos círculos em sua escultura.
-
Calcular o diâmetro correspondente aos raios mínimo e máximo para destacar a amplitude das dimensões dos círculos.
-
Calcular a área correspondente aos raios mínimo e máximo para ilustrar a diversidade de áreas cobertas pelos círculos.
-
Determinar a média dos raios para mostrar a proporção média dos tamanhos.
-
Saber quantos círculos diferentes você utilizou em sua escultura.
-
Apresentar a lista de raios em ordem crescente e decrescente para enfatizar o equilíbrio artístico.
-
Calcular a variação dos raios para demonstrar a diversidade na escolha de tamanhos.
-
Destacar a mediana dos raios para mostrar uma dimensão central dos círculos usados.
circulos = [2, 4, 3, 6, 20, 17, 5, 11, 9, 13, 2, 20]
Neste exercício, a documentação oficial do módulo Enum vai ser de grande ajuda. Sinta-se à vontade para explorar os recursos e funções oferecidos por este módulo para realizar as operações desejadas em sua análise de dados. Boa sorte em seu concurso de escultura de círculos!"
defmodule CalculoEsculturas do
def raio_minimo() do
# A fazer
end
def raio_maximo() do
# A fazer
end
def diametro_minimo() do
# A fazer
end
def diametro_maximo() do
# A fazer
end
def area_minima() do
# A fazer
end
def area_maxima() do
# A fazer
end
def media_raios() do
# A fazer
end
def numero_de_circulos() do
# A fazer
end
def ordena_raios() do
# A fazer
end
def ordena_raios_descendente() do
# A fazer
end
def variacao_raios() do
# A fazer
end
def mediana_raios() do
# A fazer
end
end