JB

JavaScript: Higher-order e First-class functions

javascripttypescriptfunctionalhigher-order functionfirst-class functionfunction
Oct 27, 2021

-- translation in progress

Existem 2 recursos que estão presentes no JavaScript que abrem um mar de possibilidades para a escrita de um código mais funcional, esses recursos são as funções de alta ordem (Higher-order functions) e as funções de primeira linha (First-class functions e/ou First-class citizens).

https://miro.medium.com/max/640/1*uVwhsCEquQHmxm1shQQNDg.png

Pixel art que eu fiz no que eu penso quando ouço “Função de Primeira Linha/Alta ordem”

Higher-order functions

“Função de alta ordem” define uma função que tem a capacidade de poder manipular outras funções, seja podendo receber funções como argumento e/ou podendo retornar outras funções.

Um caso em que podemos aplicar isso rapidamente, como exemplo, seria se tivéssemos várias funções que executam cálculos e quiséssemos interceptar algo que ocorra antes ou depois de todos cálculos e para isso podemos transferir a responsabilidade de executar o cálculo para uma outra função, sendo essa última uma Higher-order function.

Função que recebe outra função como argumento

Começarei escrevendo uma função que faz a soma de infinitos números:

https://miro.medium.com/max/1844/1*B7f3_ef7Sn5nwn4FTZiqaA.png

https://miro.medium.com/max/1964/1*iMyUJCEXTPCblYPnWWhakw.png

Função que recebe uma quantidade indefinida de números e retorna a soma de todos os números que receber.

Utilizarei exemplos tanto em Javascript quanto em Typescript para o incentivo do uso de tipagem estática. Ainda sim, o princípio dessa função de somar, continua sendo o mesmo, seja com sua tipagem dinâmica ou estática. Mesmo que com a inferência de tipos, o Typescript consiga deduzir grande parte dos tipos do exemplo, mas para fins didáticos explicitarei a maioria dos tipos. Também, disponibilizarei o arquivo dos códigos ao final do artigo.

Basicamente no código acima, com o uso do REST Operator (três pontos), a função consegue armazenar em uma variável de lista (Array) um número indefinido de argumentos. A seguir, com o uso do método reduce percorremos essa lista derivada dos argumentos recebidos e somamos todos elementos da lista.

Note que a diferença para Typescript é que com o uso da sintaxe de Type annotations torno explicito que o resultado da aglutinação dos argumentos em uma lista deve ser uma lista de números (number) e que o resultado da soma de todos números é um valor de tipo numérico (number).

Após isso, podemos fazer a função que executará o cálculo, que será a Higher-order function:

https://miro.medium.com/max/2000/1*OcxSeMfzZxl0XxKFc3L-PQ.png

https://miro.medium.com/max/2904/1*_Q-6N26At4-_UJsnFNsH4w.png

Função que recebe uma outra função e quantidade indefinida de números e retorna o resultado da execução da função recebida, tendo sido invocada com a quantidade indefinida de números.

Perceba, que essa função “calculate” consegue receber outra função como argumento, logo essa é uma função de alta ordem (higher-order function), ela também recebe uma quantidade indefinida de números como argumento, para que, então intercepta os números recebidos e execute essa função que foi recebida com a quantidade indefinida de argumentos. Essa prática de uma função receber outra função e executar, também é conhecida como function callback, mas também é possível encontrar isso sendo referenciado somente como “Callback”.

No Typescript, ocorre que declarei um Type Alias que chamei de mapeador de cálculos (Calculations Mapper) para a assinatura de uma função que recebe indefinidos números e retorna um número como resultado de sua execução. E em seguida, coloco que a “fn” que a função recebe terá assinatura de mapeador de cálculos citada, por fim torno explícito que a quantidade indefinida de argumentos será aglutinada em uma lista de números e o resultado será do tipo numérico. É notável que o código ficou mais verboso, entretanto também mais descritivo e documentado, pois com Javascript não é evidente que o parâmetro “fn” deva ser passado com um argumento de função, muito menos que tipo de função.

Agora, basta executar a função que você verá que funciona com mínimos argumentos e também com muitos:

https://miro.medium.com/max/1876/1*pi65jEmB0wDoA3ZUFxoCfA.png

https://miro.medium.com/max/2560/1*krjC82z45OD9if5H9BiL8w.png

Duas invocações da função calculate, passando a função sum como primeiro argumento e números 2 e 2 na primeira execução e tendo como resultado 4 e na segunda execução passando a função sum como primeiro argumento e como demais argumentos os números: 1, 2, 3, 4, 5, 6, 7, 8 e 9, e tendo como resultado 45.

Com essas invocações e resultados, concluo que esse experimento de uma função receber outra função como argumento elucida bem ideia de uma função de alta ordem.

Função que retorna outra função

Modificarei a estrutura já feita para que haja uma função que retorne outra função como resultado de sua execução.

Para isso, na função calculate, invés de passar a função que será executada como primeiro argumento, passarei um texto com o símbolo da operação que gostaria que fosse executada, e a função calculate, utilizará outra função chamada “getCalculateOperationBySymbol” que receberá o símbolo recebido e retornará a função da operação relacionada ao símbolo recebido.

Antes disso, criarei uma função que subtraí indefinidos argumentos que será bastante semelhante a função de soma:

https://miro.medium.com/max/1868/1*CIQl2Y_0jw0K2QBlNOOHAw.png

https://miro.medium.com/max/2120/1*4Ae1u1-feQ0gLEiHHqUxSA.png

Função que recebe uma quantidade indefinida de números e retorna a subtração de todos os números que receber.

Veja que essa função chamada de “subtract”, é idêntica a função “sum”, exceto pelo motivo de que há o sinal de subtração entre a e b, no caso “b” seria o número atual, e “a” seria o resultado da última subtração retornando pela subtração de “a” menos “b”.

Agora, vou construir a função “getCalculateOperationBySymbol”:

https://miro.medium.com/max/2088/1*pD19qxdstTziKZN8JHqqrw.png

https://miro.medium.com/max/3308/1*06fdes8hWfnTO3Za4B8kgA.png

Função que recebe um símbolo como argumento e retorna a operação aritmética referente ao símbolo recebido.

Nessa função, que recebe o texto do símbolo da operação contém um objeto chamada “operations” (estrutura de chave-valor), onde as chaves são as operações e os valores são as funções referentes ao símbolo da operação. E então, ao usar sintaxe de “operations[symbol]” estou acessando um valor que é a referência da função selecionada e é retornada essa referência de função, ou seja temos outra Higher-order-function, entretanto sendo tipo que retorna uma função diferentemente da anterior.

No Typescript, declarei um Type Alias chamado “OperationSymbols” que usa de Union Types usando valores como tipos, que basicamente é uma estrutura pode definir que uma variável somente poderá possuir os valores “+” ou “-” e inclusive atribuo esse tipo para o argumento symbol da função “getCalculateOperationBySymbol”. Também explicito que o retorno da função será uma função da assinatura “CalculationsMapper” que vimos anteriormente e coloco a tipagem de Record que é um tipo genérico para dizer que o objeto possuirá chaves do tipo “OperationSymbols” e valores do tipo “CalculationsMapper”.

Entretanto, vale ressaltar que não precisaria colocar o retorno da função pois o Typescript consegue inferir isso, e na variável operations o typescript também consegue inferir o tipo dela, embora não seja o caso, eu particularmente prefiro adicionar tipos no retorno de funções, argumentos e relações contratuais (shape de objetos que transitam por camadas).

Por fim, precisamos refatorar a função “calculate” para ela utilizar essas estruturas novas:

https://miro.medium.com/max/2716/1*sM4TGOKqk3WdjyWUHKkjEw.png

https://miro.medium.com/max/2968/1*HrktYS4u1MMMpZxeB5Nx4g.png

Função que recebe um símbolo aritmético como primeiro argumento e a partir do segundo argumento uma quantidade indefinida de números e retorna o resultado da operação aritmética desses números referente ao símbolo e números recebidos.

Na refatoração da função calculate, fizemos com que ela deixasse de ser uma Higher-order function. Invés dela receber uma “fn” e invocar essa “fn” com os números recebidos, ela passou a receber um “symbol” e ela invoca a função “getCalculateOperationBySymbol” passando o “symbol” como argumento, assim obtendo o retorno que chamei de calculateOperation e é uma função. Após isso, ela faz um log dos números e invoca essa função com os números recebidos e retorna o resultado dessa operação.

Finalmente, com a ideia de função que retorna outra função elucidada, vamos às funções de primeira linha!

First-class functions

Função de primeira linha ou função de primeira classe é quando uma linguagem de programação possibilita que funções possam ser tratadas como valores, ou seja como uma variável qualquer.

Nesse sentido, além da função conseguir receber uma outra função pelos parâmetros e retornar funções, também poderiamos guardar funções como valores.

Para exemplificar, esse tipo de função, vou converter as funções dos exemplos anteriores em funções como valores, irei converter as funções de soma para arrow function e então tratá-las como valores, que seria colocando-as dentro de variáveis.

https://miro.medium.com/max/2620/1*UE005ozPZAYqy7woyh2rxw.png

https://miro.medium.com/max/3276/1*41IBdNz_dw3MDrH8CK2QXg.png

Função de soma e subtração declaradas em formato de arrow function como valores.

Nota-se que o uso de Arrow function torna a sintaxe mais enxuta, mas também menos óbvia, principalmente, para desenvolvedores JavaScript menos experientes. Ainda sim, um ponto bacana no uso do TypeScript, foi o reuso da assinatura da função CalculationsMapper para definir o tipo dos parâmetros e retorno das funções de operações aritméticas.

https://miro.medium.com/max/2716/1*1vubfqGx3yhKfENnNbKFiw.png

https://miro.medium.com/max/3436/1*SEwil4Eed80xx3NAosq_tA.png

Função de calcular uma operação entre números e outra função de pegar o cálculo por um símbolo.

Nessa outra transformação, tanto no JavaScript quanto no TypeScript a diferença aparenta ser apenas meramente visual. Mas, dependendo do que você estiver fazendo usar uma função comum ou uma Arrow function tem grandes diferença pelos funcionamento do contexto de “this” no JavaScript.

Casos em que já utilizei

Para aprofundar um case onde esses recursos seriam utilizados, tenho dois exemplos interessantes:

Primeiramente, uma calculadora com interface feita com Angular, que foi um projeto da faculdade, no qual junto de minha companheira Viviane Queiroz utilizamos uma lógica semelhante a descrita ao longo do artigo, segue o link do repositório.

E também algum tempo atrás quando eu estava experimentando os recursos do Next.js eu apliquei Higher Order Function para obter reutilização de código que gerencia um comportamento da interface. Sendo esse comportamento a validação se o usuário está autenticado e se não estiver com um token válido, então redireciona o usuário para o root. Este é o link do repositório, sendo o código utilitário nesse arquivo e com utilização nesse outro arquivo.

Fora esses dois casos de projeto da faculdade e de estudo, também já apliquei bastante essa técnica em problemas do dia a dia nas empresas em que trabalhei.

Por fim, recomendo às seguintes leituras extras e arquivos do código usado como exemplo:

Continue explorando os recursos da linguagem para que possa implementar soluções simples, eficazes e criativas.

Sapere aude.