Programação Funcional
A programação funcional é de longe uma das mais divertidas entre as escritas até agora. (veja mais sobre) Ignore tudo que você aprendeu nos artigos da procedural e da orientada a objeto.
As exemplificações deste artigo só foram possíveis por inspirações de pessoas como ThePrimeagen, CodeAesthetic, Fireship, e Coderized.
Funções
O que são funções? Tão quão na matemática, funções são expressoes que transformam uma entrada X em uma saída Y.
Fundamentos
- Variáveis são imutáveis
- Funções podem ser passadas como parâmetros para outras funções
- Funções podem ser retornadas de outras funções
- Pureza de funções
Imutabilidade
Todas as variáveis são imutáveis.
Quando atribuímos um valor a uma variável, não estamos modificando ela. Estamos criando uma nova, e descartando o valor antigo.
Funções como parâmetros
Podemos dar a uma função outra função como parâmetro. Isto modulariza o código de maneira em que a leitura do código fica mais fácil, os testes ficam mais simples e a depuração, trivial.
Uma das funcionalidades mais legais da linguagem Elixir é a múltipla definição de funções, que serve como pattern matching.
def filtrar([], _), do: []
def filtrar([argumento | lista], condicao) do
if condicao.(argumento) do
[argumento | filtrar(lista, condicao)]
else
filtrar(lista, condicao)
end
end Agora temos uma função em elixir que filtra os itens de uma lista dada uma condição, que por sinal é uma função, que recebe o item da lista que está sendo filtrado.
Não se assuste com o desconhecido, se nunca viu nenhum código em elixir. É até que bem simples (e bem útil).
O que está acontecendo é:
Na primeira definição, se a lista estiver vazia, retornaremos uma lista vazia. Este é nosso caso base. Onde a função sabe quando parar, nesse caso, quando não houverem itens para filtrar.
Já na segunda, estamos retirando o primeiro
argumentodalistano parâmetro[argumento | lista]. Logo após, se a condição for verdadeira, retornamos uma lista com ou sem oargumento, respectivamente.[x | y]como parâmetro de uma função separa ox(item) de uma listay. No entanto, a mesma linha em meio ao código significa que estamos adicionando um item ao início dela. (documentação oficial)
É sempre interessante visualizar o que está acontecendo, por exemplo:
- Criamos uma lista: [1, 2, 3, 4, 5]
- Chamamos a função:
[1, 2, 3, 4, 5] |> filtrar(fn x -> x > 2 end)1->[2, 3, 4, 5]2->[3, 4, 5]3->[4, 5]4->[5]5->[]- Nenhum item para filtrar
5 > 2? Sim:[5 | ...]->[5]4 > 2? Sim:[4 | [5 | ...]]->[4, 5]3 > 2? Sim:[3 | [4 | [5 | ...]]]->[3, 4, 5]2 > 2? Não:[...]->[3, 4, 5]1 > 2? Não:[...]->[3, 4, 5][3, 4, 5]
Retornando funções
Podemos também retornar funções, deste modo salvamos o estado temporariamente e podemos reutilizá-lo a nosso favor.
def multiplicar(x), do: fn y -> x * y end Desta forma, se executarmos multiplicar(2).(5), obteremos 10 como resultado.
Mas a grande vantagem é que podemos ter uma variável que carrega a função:
multiplicar_por_dois = multiplicar(2) Agora, sempre que chamarmos multiplicar_por_dois, bom… o nome é bem sugestivo.
Pureza de funções
Na programação funcional existe um conceito bem simples de “pureza de funções”. Na prática, o que isso diz é se uma função sempre retornará a mesma saída dada uma mesma entrada.
Funções puras
Estas são bem simples:
def calcular(x), do: x + x Essa função sempre retorna o dobro de x.
Funções impuras
Ao executarmos uma função, se algum valor externo alterar a execução do código, ou o nosso código alterar algum valor externo, dizemos que esta função é impura.
Diz-se, então, que essas funções causam efeitos colaterais, ou em inglês, side-effects. Por exemplo, em javascript:
let n = 0;
function calcular(x) {
const valor = x + n;
n = x;
return valor;
} Agora nossa função retorna valores diferentes, dependendo dos anteriores.
Se em algum momento eu chamar a função calcular com o valor 2, e depois 2 denovo, na primeira vez eu teria ‘2’ e na segunda, 4.
Os valores mudaram, mesmo que a entrada não.
O inverso também se aplica, se nossa função modificar algum valor externo ela já não é mais pura.
Por coincidência, esta função altera, sim, um valor externo. A variável n.
Mas um ótimo exemplo são displays de vídeo. Ao renderizarmos uma imagem em uma tela, estamos modificando um estado externo: os valores dos pixeis da tela! Por isso que em diversas linguagens funcionais funções relacionadas à saídas em terminais ou displays, são restringidas ou, pelo menos, tratadas diferentes das outras.
Um exemplo em haskell é que para imprimir uma mensagem no console, é necessário que a chamada da função esteja dentro de uma função que retorne um monad que permita causar efeitos colaterais. (
main :: IO())
Conclusão
Com isso, aprendemos como um programa é estruturado na linguagem Elixir, conceitos básicos da programação funcional como imutabilidade, recursividade, funções como parâmetros, funções que retornam funções, pureza de funções, efeitos colaterais e escopos.
Ver mais
Veja outros artigos sobre linguagens de programação: