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
argumento
dalista
no 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: