Executando verificação de segurança...
21

Programação Funcional Resumida

Introdução

Hoje venho trazer um resumo bem tranquilo para explicar o quê é programação funcional.
Inicialmente, vamos começar fazendo um contraponto com a programação imperativa.

Programação Imperativa

No código seguinte definimos uma função que retorna o quadrado da soma de duas variáveis.

public float squared_sum(x,y) {
	sum = x+y;
	squared = sum**2;
	return squared
}

Essa função faz com que o computador guarde na memória a variável soma, a variável squared e os respectivos valores. Esses são chamados efeitos colaterais.

Programação Funcional/Declarativa

Agora vamos transformar o mesmo código para programação funcional.

public float squared_sum(x,y){
	return (x+y)^2;
}

Note-se que retiramos as variáveis e fizemos todas as operações no próprio return. Essa mudança elimina os efeitos colaterais que falamos antes , ou seja, nesse caso não estamos mais ocupando espaço na memória para fazer essa operação.

Mas o quê é afinal programação funcional?

Programação funcional é um método de resolução de problemas que tem como objetivo construir software por meio de composição de funções puras, evitando compartilhamento de estados, dados mutáveis e efeitos colaterais.

Funções Puras

Uma função é chamada de pura quando, se invocada mais de uma vez com os mesmos parâmetros, produz exatamente o mesmo resultado.
Além disso, é necessário que ela não cause efeitos colaterais externos como imprimir linha de saída, alterar algo no banco, guardar algo na memória, etc.

Composição de funções

Com esses conceitos em mente, podemos fazer composições de funções, tornando nossas operações mais complexas. Pode parecer algo diferente, mas você já pode ter visto em diversas situações.

Em suma, isso significa criar uma nova função por meio da composição de outras.

No exemplo seguinte temos uma função que filtra um array retirando apenas os números pares e, por fim, multiplicando por 2.

const numeros = [2,3,4,5,6,7,8,9,10]

numeros.filter((numero) => numero % 2 === 0).map((numero) => numero * 2) 
// [ 4, 8, 12, 16, 20 ]

Fontes


Sobre mim:

Estou compartilhando um pouco das minhas notas sobre os conteúdos que estudo. Se sinta livre para continuar a discussão. Espero que tenha ajudado :)

Github: https://github.com/JuliaGK

Carregando publicação patrocinada...
3

Algumas correções

Essa função faz com que o computador guarde na memória a variável soma, a variável squared e os respectivos valores. Esses são chamados efeitos colaterais.

Nop, essa não é a definição de efeito colateral. São apenas variáveis (estado) mesmo, só isso. Efeito colateral é quando uma função altera um estado fora de seu escopo. Por exemplo ao alterar uma variável global, um objeto passado por referência, memória apontada por um ponteiro passado como argumento para a função, o conteúdo de um arquivo etc.

Se essas variáveis aí são locais e, por consequência disso, só podem ser acessadas dentro da própria função, então não há efeito colatoral em alterar o valor delas. Se a ideia do exemplo era dizer que as variáveis são globais, então faltou esclarecer isso e explicar direito o conceito.

Agora vamos transformar o mesmo código para programação funcional.

Por que você acha que simplesmente remover as variáveis mudou magicamente de "imperativo" para "funcional"? As duas funções de exemplo que você deu poderiam muito bem serem usadas em paradigma imperativo ou funcional, uma coisa não tem nada a ver com a outra. Você trocou 6 por meia dúzia aí.

O paradigma funcional diz respeito a maneira que você escreve o código do programa e não o código de uma função específica. Mesmo que um programa escrito em paradigma funcional tivesse uma função com efeitos colaterais, isso não faria com que automagicamente o programa deixase de ter sido feito em paradigma funcional.

Do mesmo jeito jeito que o código seguindo Programação Orientada a Objetos (POO), por ter um ou outro código seguindo o paradigma procedural, não deixa de ser um programa em POO.

E seguindo essa linha, também criar uma classe e implementar uns métodos nela não faz com que seu código seja POO. Você pode muito bem estar escrevendo código procedural usando classes (o que ocorre com muita frequência, vale dizer).

Note-se que retiramos as variáveis e fizemos todas as operações no próprio return. Essa mudança elimina os efeitos colaterais que falamos antes , ou seja, nesse caso não estamos mais ocupando espaço na memória para fazer essa operação.

Não existe garantia nenhuma de que o código gerado não precisará de acessos à memória. Isso depende da convenção de chamada e do que o compilador vai fazer para gerar o código dessa função.

Uma função é chamada de pura quando, se invocada mais de uma vez com os mesmos parâmetros, produz exatamente o mesmo resultado.
Além disso, é necessário que ela não cause efeitos colaterais externos como imprimir linha de saída, alterar algo no banco, guardar algo na memória, etc.

Guardar algo na memória principal não faz com que a função deixe de ser pura. Usar a memória é simplesmente inevitável para qualquer software, escrito em qualquer linguagem e em qualquer paradigma. O processador não tem registradores suficientes para que seja possível evitar usar a memória. E mesmo que tivesse há objetos que são grandes demais para ficarem em um registrador (como arrays, listas, estruturas etc.) e há operações em que isso é simplesmente inevitável. Além de estruturas como a hardware stack que, obviamente, fica na memória.

Em outras palavras, mesmo linguagens puramente funcionais como Haskell irão "guardar algo na memória".

Com esses conceitos em mente, podemos fazer composições de funções, tornando nossas operações mais complexas. Pode parecer algo diferente, mas você já pode ter visto em diversas situações.

Em suma, isso significa criar uma nova função por meio da composição de outras.

No exemplo seguinte temos uma função que filtra um array retirando apenas os números pares e, por fim, multiplicando por 2.

Não é isso que significa composição de função e o exemplo dado não é composição de função. "Compor função" é bem mais simples do que isso. É simplesmente passar o retorno de uma função como argumento para outra. Tipo isso:

const result = square(sum(x, y));

Ou então você pode declarar uma nova função fazendo a composição de outras funções. Assim:

function squared_sum(x, y) {
  return square(sum(x, y));
}

Veja que eu passo o retorno da função sum como argumento para a função square, que por sua vez é o retorno da função squared_sum. Isso é composição de função. Simples assim.

Dicas

  1. Qualquer material que tenha "for dummies" ou "para leigos" no título é uma péssima referência. Quando o autor do conteúdo coloca isso no título, 99% das vezes é porque ele mesmo é leigo no assunto mas decidiu falar à respeito porque sim.
  2. Programação funcional é baseada em lambda calculus. Estude como esse treco funciona que tu vai entender o cerne de FP.
  3. Sugiro aprender uma linguagem puramente funcional como Haskell, mesmo que não vá de fato programar nela "para valer". Se não as chances são enormes de que você escrever código procedural achando que é funcional, só porque decorou o nome de alguns conceitos de FP
  4. Toda vez que tu ver um conceito que tu não conhece jogue ele no Google, consulte o artigo na Wikipédia (e as referências), veja vários artigos diferentes, vários vídeos diferentes, várias discussões sobre o assunto em fóruns/blogs/stack overflow etc. Não confie no que uma pessoa em um artigo tem a dizer sobre o assunto.
1

Programação funcional é um termo muito abrangente e se refere a multiplas características que linguagens podem ou não podem ter, muitos autores se discordam quanto à classificar uma linguagem como funcional ou não, mas fato é que o autor do post mostrou algumas características de linguagens funcionais (ainda que forma errônea).

Haskell por exemplo é considerado uma linguagem funcional, tendo avaliação preguiçosa não permitindo que efeitos colaterais aconteçam (somente se você colocar em estruturas bem definidas).

Elixir também é considerada uma linguagem funcional mas possui o método IO.puts que é um efeito colaterál.

Também, Scala é funcional mas também possui features de orientação à objeto.

Até mesmo o JavaScript, Python e Java podem ser funcionais (se levarmos em conta que elas permitem:

  • criação de lambdas
  • o uso de funções como map, filter e reduce (funções de alta ordem)
  • recursão

Reforço a resposta do comentário acima, é muito fácil assistir um vídeo no youtube sobre esse estilo e achar que sabe tudo sobre ela, programação funcional é algo que data até mesmo antes da criação da máquina de Turing, e aprender as bases dela através do cálculo lambda é uma ótima maneira de entender mais sobre esse paradigma.

Recomendo a playlist da UFABC que ensina esse estilo utilizando Haskell

Caso eu esteja errado favor me corrigir!

1

Como podemos saber até que ponto vale trocarmos performance por legibilidade de código?

Digo...
Para o exemplo exposto no artigo:

const numeros = [2,3,4,5,6,7,8,9,10]

numeros.filter((numero) => numero % 2 === 0).map((numero) => numero * 2) 
// [ 4, 8, 12, 16, 20 ]

Tudo bem, é uma pequena lista com dados primitivos.
Mas, e se fosse um grande array, mais complexo, com dados bem mais complexos?

Digo, um array com 18 mil usuários, por exemplo.


const isActive = user => user.status.current === ActiveStatus.ACTIVE;
const isFromBrasil = user => user.region === 'BR';
const getFullName = user => `${user.name} ${user.lastName}`; 

const brazilianActiveUsersName = aBigUsersArray
                                     .filter(isFromBrasil)
                                     .filter(isActive)
                                     .map(getFullName) 

Isso não aumentaria o processamento por ter de iterar o mesmo array X vezes, onde X é a quantidade de operações/tratamentos que fazemos em cima dos dados?

1

Compiladores já fazem otimizações que convertem códigos desse estilo em uma abordagem mais rápida, desse jeito você tem a legibilidade do código + velocidade

Além do mais, ao contrário de décadas passadas onde memória era extremamente cara e a performance dobrava (Lei de Moore) e precisava ser bem gerenciada, hoje, memória RAM é muito barata mas a Lei de Moore está acabando e precisamos usar mais núcleos da CPU e consequentemente programas precisam utilizar computação paralela (Algo que linguagens funcionais são muito boas)

1

Pelo que eu entendo, você poderia muito bem reutilizar a contante brazilianActiveUsersName, já que isso não provocaria nenhuma alteração de estado no programa.

1

Sim, claro. Não digo ter que refazer os cálculos para chegar ao mesmo array armazenado na constante.
O que eu questionei foi o fato de ter de realizar 3 iterações sobre um array hipotético de 18 mil itens, por exemplo, para, então, chegar à constante brazilianActiveUsersName.

1

Há algum medo de em busca de uma função declarativa, você não utilizar de variáveis que poderiam melhorar a legibilidade do seu código ?

1

Eu acredito que você não perde nada em legibidade. Ao invés de ter várias variáveis com nomes sugestivos, você deve ter várias funções com nomes sugestivos.

1
1
1
1

Existe algum ganho em trabalhar com linguagens de programação ditas funcionais (haskell, clojure, erlang) ao invés de aplicar conceitos da programação funcional em linguagens multiparadigma (python, js, java)?

1

Boa explicação.
Um adendo pessoal que eu gostaria de colocar aqui em pauta, é que pra mim FP virou a modinha do momento, assim como já foi POO, JAVA e etc.
Nada contra, aprender o básico de FP é um bom fundamento.

1

Uma pergunta sobre o assunto apesar de não alocar memória, isso exige menos ou mais do computador e performa melhor ou pior?
Vamos supor que eu faça isso em um bloco de funções que os valores serao os mesmos, estarei poupando a utilização de memória. Porém se eu utilizar constantemente esse processo a máquina vai constantemente ter que processar esses valores já que ela não tem o valor armazenado.

Alias otima explicação, provavelmente eu não teria tido acesso a essa informacao ( •̀ ω •́ )✧

1

Bem legal, eu gosto como o Javascript trabalha essa parte declarativa, alguns exemplos sao os metodos map, fikter e reduce. React tambem trabalha com composicao de funcoes, que é uma tecnica bem legal.