Entendendo a Programação Funcional e o Uso do RxJS

A programação funcional, embora antiga, ganhou destaque nos últimos anos. Linguagens como Go, Scala, Haskell e Clojure, que adotam paradigmas funcionais, estão em ascensão. Essa influência é notável no desenvolvimento frontend, com frameworks populares como Angular, React e Vue incorporando conceitos funcionais para resolver problemas complexos.

A programação funcional é um paradigma de programação de alto nível, aproximando-se da matemática e da álgebra, e difere significativamente da orientação a objetos. Compreender essa diferença é crucial para o aprimoramento de um desenvolvedor.

Paradigmas de Programação

Um paradigma de programação é um estilo ou padrão na concepção de linguagens de programação, refletindo as considerações do designer e influenciando a forma como os desenvolvedores escrevem código. Eles podem ser amplamente categorizados:

Conceitos e Pensamento Funcional

A programação funcional baseia-se no cálculo lambda. Embora a definição formal possa parecer intimidante, muitos de seus conceitos já são utilizados no desenvolvimento diário. O JavaScript moderno, com recursos do ES6 como as funções de seta (arrow functions), inspiradas em linguagens como CoffeeScript e Scala, adicionou características funcionais à linguagem. As funções de seta são funções anônimas com escopo léxico.

O objetivo da programação funcional é minimizar os efeitos colaterais de uma computação para dados além do resultado.

Características da Programação Funcional

  1. Declarativa: Descreve o que deve ser feito, em vez de como fazer.
  2. Funções Puras:
    • A execução de uma função depende unicamente de seus parâmetros de entrada, sem influências externas.
    • Funções puras não modificam nenhum estado externo, como variáveis globais ou parâmetros passsados por referência.
  3. Imutabilidade de Dados: Ao modificar dados, em vez de alterar o estado original, um novo conjunto de dados é criado para refletir a mudança. Dados imutáveis, uma vez criados, não podem ser alterados, o que facilita a compreensão do código.

Compare os estilos imperativo e funcional:


// Dobra os elementos de um array

// Programação Imperativa
function dobrarImperativo(arr) {
 const resultados = [];
 for (let i = 0; i < arr.length; i++) {
   resultados.push(arr[i] * 2);
 }
 return resultados;
}

console.log(dobrarImperativo([1, 2, 3])); // Saída: [2, 4, 6]

// Programação Funcional
function dobrarFuncional(arr) {
 return arr.map(item => item * 2);
}

const arrayOriginal = [1, 2, 3];
const arrayDobrado = dobrarFuncional(arrayOriginal);

console.log(arrayOriginal); // Saída: [1, 2, 3]
console.log(arrayDobrado); // Saída: [2, 4, 6]
 

Funções como Cidadãos de Primeira Classe

Assim como números, funções em JavaScript são cidadãos de primeira classe, possuindo propriedades semelhantes:

  1. Podem ser atribuídas a variáveis: ```

    let funcaoUm = function() { return 1 };

  2. Podem ser armazenadas em estruturas de dados como arrays: ```

    let listaFuncoes = [1, function() { return 1 }];

  3. Podem ser passadas como argumentos para outras funções: ```

    function somarComFuncao(n, f) { return n + f() }; somarComFuncao(1, function() { return 1 }); // Saída: 2

  4. Podem ser retornadas por outras funções: ```

    return 1; return function() { return 1 };

    
    

As duas últimas propriedades definem funções de ordem superior.

Funções de Ordem Superior

Uma função de ordem superior é aquela que opera em outras funções, seja recebendo-as como argumento ou retornando-as. Elas são a base para a implementação de estilos funcionais em linguagens de programação. Um exemplo no React são os Componentes de Ordem Superior (HOCs).

Exemplo de função para adicionar separador de milhar:


const adicionarSeparadorMilhar = (valor) => {
   return parseFloat(valor).toString().split('.').map((parte, indice) => {
       if (indice === 0) {
           return parte.split('')
                      .reverse()
                      .map((digito, idx) => (idx > 0 && idx % 3 === 0) ? (digito + ',') : digito)
                      .reverse()
                      .join('');
       } else {
           return parte;
       }
   }).join('.');
};
 

Aplicação de Ordem Superior: Currying

Currying transforma uma função que recebe múltiplos argumentos em uma sequência de funções que recebem um único argumento cada. Isso permite a criação de APIs mais fluidas e funcionais. Embora Haskell adote currying por padrão, em JavaScript é necessário implementá-lo explicitamente.


function curry(funcao) {
   return function(arg) {
       return funcao(arg);
   };
}

const arrNumerosString = ['1', '2', '3', '4'];
const arrNumeros = arrNumerosString.map(curry(parseInt));
console.log(arrNumeros); // Saída: [ 1, 2, 3, 4 ]
 

Recursão

Recursão é uma técnica onde uma função chama a si mesma para resolver um problema. É uma forma poderosa de definir conjuntos infinitos de objetos com um número finito de instruções. Uma função recursiva geralmente possui uma condição de parada (caso base), um passo recursivo e um valor de retorno.

É importante notar a diferença entre recursão comum e recursão de cauda (tail recursion). Em implementações que não otimizam recursão de cauda, chamadas recursivas excessivas podem levar a estouro de pilha (stack overflow). A otimização de recursão de cauda permite que a chamada recursiva seja tratada como um salto, evitando o acúmulo na pilha de chamadas.


// Recursão comum (pode estourar a pilha)
const fatorialRecursivo = (n) => {
 if (n <= 1) {
   return 1;
 } else {
   return n * fatorialRecursivo(n - 1);
 }
};
console.log('Fatorial Recursivo: ', fatorialRecursivo(5)); // Saída: 120

// Recursão de cauda (otimizável)
const fatorialCauda = (n, acumulador = 1) => {
 if (n <= 1) {
   return acumulador;
 } else {
   return fatorialCauda(n - 1, n * acumulador);
 }
};

console.log('Fatorial de Cauda: ', fatorialCauda(5)); // Saída: 120
 

Programação Baseada em Fluxo (Streams)

No desenvolvimento frontend, o RxJS é um representante clássico da programação baseada em fluxo (streams). Conforme descrito pela ReactiveX, RxJS é uma API para programação assíncrona com fluxos observáveis.

Introdução ao RxJS

RxJS é a implementação em JavaScript do padrão Reactive Extensions. Ele fornece uma biblioteca para compor programas assíncronos e baseados em eventos usando sequências observáveis. Seu tipo central é o Observable, juntamente com Observers, Schedulers e Subjects, e operadores como map e filter.

O RxJS opera na interação entre Observable e Observer. Um Observable representa um fluxo de dados ou eventos, implementando os padrões Observer e Iterator. A conexão antre eles é estabelecida através do método subscribe.

Conceitos Fundamentais do RxJS

  • Observable: Representa uma coleção de valores ou eventos futuros. É o "publicador". ```

    import { of } from 'rxjs';

    const fonte$ = of(1, 2, 3); // Publicador fonte$.subscribe(console.log); // Observador: Saída 1, 2, 3

  • Observer: Um conjunto de funções de callback (next, error, complete) para lidar com os valores emitidos pelo Observable.

  • Subscription: Representa a execução de um Observable e é usada para cancelar essa execução. ```

    import { Observable, interval } from 'rxjs'; import { take } from 'rxjs/operators';

    const meuObservable = new Observable(observer => { let contador = 0; const idIntervalo = setInterval(() => { console.log(Emitindo: ${contador}); observer.next(contador++); }, 1000);

    return () => { // Função de limpeza (desinscrição)
      console.log('Desinscrito.');
      clearInterval(idIntervalo);
    };
    

    });

    const subscricao = meuObservable.pipe(take(3)).subscribe( valor => console.log(Recebido: ${valor}), erro => console.error(erro), () => console.log('Completo!') );

    // Para cancelar manualmente: // setTimeout(() => subscricao.unsubscribe(), 3500);

    
    Quando `unsubscribe()` é chamado, a função de limpeza dentro do Observable é executada, parando a emissão de valores.
    
  • Operators: Funções puras que transformam Observables. Operadores como map e filter são semelhantes aos métodos de array em JavaScript.

    
    import { of } from 'rxjs';
    import { filter, map } from 'rxjs/operators';
    
    const numeros$ = of(1, 2, 3, 4, 5, 6);
    const resultado$ = numeros$.pipe(
     filter(x => x % 2 === 0), // Filtra números pares
     map(x => x * 2)          // Dobra os números pares
    );
    
    resultado$.subscribe(console.log); // Saída: 4, 8, 12
    
    

    Os operadores são classificados em categorias como criação, transformação, filtragem, combinação, etc.

  • Subject: Um tipo especial que é tanto um Observable quanto um Observer. Ele permite que múltiplos Observers "assinem" um único Observable e recebam os mesmos eventos. É semelhante a um EventEmitter. ```

    import { interval } from 'rxjs'; import { take, publish } from 'rxjs/operators'; import { Subject } from 'rxjs';

    const tick$ = interval(1000).pipe(take(3)); const subject = new Subject();

    tick$.subscribe(subject); // O Subject atua como Observer aqui

    subject.subscribe(value => console.log('Observer 1: ' + value));

    setTimeout(() => { subject.subscribe(value => console.log('Observer 2: ' + value)); }, 1500);

    
    Observadores subscritos após o início da emissão do Subject também recebem os valores.
    
  • Scheduler: Controla a ordem e o tempo de execução das notificações. Permite gerenciar a concorrência. Exemplos incluem asap, async, queue e animationFrame. ```

    import { range } from 'rxjs'; import { asap } from 'rxjs/scheduler';

    console.log('Antes da subscrição'); const numerosRange$ = range(1, 3, asap);

    numerosRange$.subscribe( value => console.log('Dado: ', value), error => console.log('Erro: ', error), () => console.log('Completo') ); console.log('Depois da subscrição');

    
    A saída demonstra a execução assíncrona controlada pelo Scheduler.
    
    

Impacto Positivo da Programação Funcional no Frontend

No desenvolvimento web, a complexidade aumenta com a quantidade de estado e eventos. A adoção de lógica funcional pode melhorar significativamente a legibilidade e a depurabilidade do código front end, especialmente em projetos complexos. Ao refatorar código para um estilo mais funcional ou usar ferramentas funcionais, a compreensão do fluxo de negócios se torna mais clara, tanto para manutenção quanto para desenvolvimento de novas funcionalidades.

O objetivo principal da programação funcional no frontend é resolver problemas de forma mais eficiente e robusta. Uma abordagem proativa, com planejamento e bons hábitos de desenvolvimento, é mais eficaz do que esperar por futuras correções ou refatorações.

Tags: Programação Funcional RxJS javascript Observables funções puras

Publicado em 9-22 02:11