Introdução a programação funcional com TypeScript e JavaScript

Uma pequena introdução ao paradigma funcional com TypeScript

O que é programação funcional ?

Com o aumento da complexidade de programas e seus inúmeros estados, estamos voltando a um paradigma antigo: o Funcional. Muitos programadores já estão acostumados com o paradigma orientado a objetos e por isso, começarei dando um comparativo. Programação orientada a objetos possui muitos padrões e regras, mas podemos resumir da seguinte forma: Programação orientada a objetos impõe disciplina sobre a transferência indireta do controle dos dados e isso em comparação a programação funcional Programação funcional impõe disciplina sobre a atribuição. Essas duas frases dizem muito sobre os respectivos paradigma, que vamos nos aprofundar nesse blog post.

O que eu ganho com isso ?

Ao saber um novo paradigma você ganha noas armas para resolver problemas antigos até mais facilmente que antes. vou mostrar um script "puro" em TypeScript e depois outro impuro em TypeScript e por fim um belo exemplo do mesmo exercicio em clojure. vou colocar abaixo o código inteiro do script e vamos aos poucos analizando e entendendo o que tem acontecido

O começo

const customFunction = (
  value1: number,
  value2: number,
  funcEvaluater: string,
  textObj: { todayText: string; tomorrowText: string }
) => {
  function createdFunction(funcEvaluater) {
    return eval(funcEvaluater);
  }

  const currier =
    (textObj: { todayText: string; tomorrowText: string }) =>
    (val2: string) =>
    (val3: string) => {
      return textObj.todayText + val2 + textObj.tomorrowText + val3;
    };

  return [value1 + value2, createdFunction(funcEvaluater), currier(textObj)];
};

const [sum, evaluator, curryWhater] = customFunction(2, 3, '3 + 3', {
  todayText: 'Today is: ',
  tomorrowText: ' and tomorrow will be: ',
});

const getTodayWheterApi = (userLocation: string) => {
  // call api
  return 'Raining';
};
const getTomorrowWheterApi = (userLocation: string) => {
  // call api
  return 'Sunny';
};
// Chama a localização do user aqui
const currentLocation = 'userCurrentLocation';
appDiv.innerHTML = `
<h2>value: ${sum}</h2>
<h3>Eval: ${evaluator} </h3>
<h3>curry: ${curryWhater(getTodayWheterApi(currentLocation))(getTomorrowWheterApi(currentLocation))} </h3>`;

Funções puras

A única forma perfeita de enpasulamento é funções. podemos ver em nossa função chamada customFunction

const customFunction = (
  value1: number,
  value2: number,
  funcEvaluater: string,
  textObj: { todayText: string; tomorrowText: string }
) => {
  function createdFunction(funcEvaluater) {
    return eval(funcEvaluater);
  }

  const currier =
    (textObj: { todayText: string; tomorrowText: string }) =>
    (val2: string) =>
    (val3: string) => {
      return textObj.todayText + val2 + textObj.tomorrowText + val3;
    };

  return [value1 + value2, createdFunction(funcEvaluater), currier(textObj)];
};

Tudo que você usa nessa função, você recebe como parametro, e ainda assim existe um retorno, logo você acaba por trabalhar, fazer algum calculo, chamada de api ou qualquer outro tratamento no fluxo de dados e por fim retorna o que você fez, nesse caso, recebemos 4 argumentos, 2 valores que serão somados e retornados por primeiro, uma função que será evaliada(não use eval em produção) e por fim, retorna também além do resultado do eval, o curry baseado em um objeto que foi passado como parametro, o curry em poucas palavras é uma função que gera outra função.

Qual o resultado disso ?

Por ser uma função pura e ser facilmente digerida, devido ser um encapsulamento perfeito, fora dela, temos os seus resultados sendo usados.

const [sum, evaluator, curryWhater] = customFunction(2, 3, '3 + 3', {
  todayText: 'Today is: ',
  tomorrowText: ' and tomorrow will be: ',
});

E esse resultado na interface do user se tornará isso daqui

const getTodayWheterApi = (userLocation: string) => {
  // call api
  return 'Raining';
};
const getTomorrowWheterApi = (userLocation: string) => {
  // call api
  return 'Sunny';
};
// Chama a localização do user aqui
const currentLocation = 'userCurrentLocation';
html do user
appDiv.innerHTML = `
<h2>value: ${sum}</h2>
<h3>Eval: ${evaluator} </h3>
<h3>curry: ${curryWhater(getTodayWheterApi(currentLocation))(getTomorrowWheterApi(currentLocation))} </h3>`;

o print abaixo mostra o resultado

resultado html

Isso deixa facilmente ajustavel qualquer parte do código uma vez que cada passo, temos uma constante resultante de uma função.

E na prática como que ficaria isso ?

Podemos olhar o caso classico de um for loop, eu fiz o mesmo for loop duas vezes em typescript para mostrar uma versão mais moderna do antigo e em meu ver até mais bonita


// for loop classico
for (let i = 0; i < 30; i++) {
  console.log(i * i);
}
// for loop moderno  -> seu array = Array.from(Array(30).keys())
for (const [index, value] of Array.from(Array(30).keys()).entries()) {
  console.log(value * value);
}
// exemplo prático do array moderno
const array = [1, 2, 3, 4, 5, 6, 7, 8];
for (const [index, value] of array.entries()) {
  console.log(value);
}

Básico não é ? é relaticamente simples e facil de ser entendido, mas ao mesmo tempo, me irrita no classico, o fato de termos uma variavél que será descartada depois.
Em Clojure temos a mesma solução desse problema, que ao meu ver extremamente lindo:


;; resolver função que faz o quadrado dos primeiros 30 numeros inteiros
(defn create-n-list
  "creates a n list"
  [numbers]
  (take (+ numbers 1) (range)))

(defn first-n-multiples "recebe n numeros e retorna uma lista com seus multiplos"
  [numbers]
  (map * (create-n-list numbers) (create-n-list numbers)))

;; (first-n-multiples 30)
;;=> (0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361 400 441 484 529 576 625 676 729 784 841 900)

por mais que eu tenha criado duas funções existem maneiras de fazer via função anonima, porem dessa forma, fica bem explicito o que é buscado a ser feito. Essa lista que foi retornada poderá ser usada como uma constante e assim por diante.
Caso sinta alguma dificuldade lendo esse código em Clojure, me mande uma DM no twitter, ficarei extremamente feliz em lhe ajudar! Maior dificuldade de muitas pessoas com o clojure é sua notação infixa, como o exemplo de sua multiplicação sendo (* n n).

Conclusão

Como conhecimento nunca é de mais, acredito que dar uma chance e tentar escrever códigos mais funcionais, com menos estados e mais funções e constantes, pode ser uma boa prática para o futuro num geral. Recomendo conhecer alguma variação de LISP e se apaixonar por como uma linguagem tão simples pode ser tão poderosa.

Contato

Se quiser discutir sobre qualquer assunto ou viu algum erro, não hesite em me marcar ou chamar no Twitter: @que_cara_legal
Estou sempre tentando trazer o que tenho estudado, as vezes traduzindo algums tópicos divertidos que me chamam atenção.