Dê uma olhada nos seguintes trechos de código que resolvem o mesmo problema e pense em qual deles você mais gosta.
Aqui está o primeiro: | Aqui está o segundo: |
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(int => isEven(int)) .filter(int => isBiggerThan(3, int)) .map(int => int + 1) .map(int => toChar(int)) .filter(char => !isVowel(char)) .join('') // 'fhjl'
| [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('') |
"Aposto que a segunda opção tem uma legibilidade muito melhor do que a primeira", diz o autor do material, cuja tradução publicamos hoje. Segundo ele, o ponto principal está nos argumentos dos métodos
filter()
e
map()
.

Hoje, falaremos sobre como reciclar código semelhante ao primeiro exemplo, para que ele se pareça com o código do segundo. O autor do artigo promete que, depois de entender como ele funciona, você se relacionará com seus programas de uma nova maneira e não poderá ignorar o que costumava parecer bastante normal e não exige melhorias.
Função simples
Considere uma função
sum()
simples que adicione os números passados a ela:
const sum = (a, b) => a + b sum(1, 2)
Nós a reescrevemos, dando à nova função o nome
csum()
:
const csum = a => b => a + b csum(1)(2)
Sua nova versão funciona exatamente da mesma maneira que a original, a única diferença é como essa nova função é chamada. Ou seja, a função
sum()
usa dois parâmetros ao mesmo tempo, e
csum()
usa os mesmos parâmetros, um de cada vez. De fato, ao chamar
csum()
, duas funções são chamadas. Em particular, considere a situação quando
csum()
chamado, passando o número 1 e nada mais:
csum(1)
Essa chamada para
csum()
leva ao fato de que ela retorna uma função que pode aceitar o segundo argumento numérico passado para
csum()
durante sua chamada usual e retorna o resultado da adição de uma a esse argumento. Chame esta função
plusOne()
:
const plusOne = csum(1) plusOne(2)
Trabalhar com matrizes
Em JavaScript, você pode trabalhar com matrizes usando uma variedade de métodos especiais. Digamos que o método
map()
seja usado para aplicar a função passada a ele em cada elemento da matriz.
Por exemplo, para aumentar em 1 cada elemento de uma matriz inteira (mais precisamente, para formar uma nova matriz contendo elementos da matriz original aumentada em 1), você pode usar a seguinte construção:
[1, 2, 3].map(x => x + 1)
Em outras palavras, o que está acontecendo pode ser descrito da seguinte forma: a função
x => x + 1
pega um número inteiro e retorna o número que o segue em uma série de números inteiros. Usando a função
plusOne()
discutida acima, este exemplo pode ser reescrito da seguinte maneira:
[1, 2, 3].map(x => plusOne(x))
Aqui vale a pena desacelerar e pensar no que está acontecendo. Se você fizer isso, poderá ver que, no caso em consideração, as construções
x => plusOne(x)
e
plusOne
(observe que nessa situação não há colchetes após o nome da função) são equivalentes. Para entender melhor isso, considere a função
otherPlusOne()
:
const otherPlusOne = x => plusOne(x) otherPlusOne(1)
O resultado desta função será o mesmo que o obtido por uma simples chamada para o
plusOne()
já conhecido por nós:
plusOne(1)
Pela mesma razão, podemos falar sobre a equivalência das duas construções a seguir. Aqui está o primeiro que já vimos:
[1, 2, 3].map(x => plusOne(x))
Aqui está o segundo:
[1, 2, 3].map(plusOne)
Além disso, lembre-se de como a função
plusOne()
foi criada:
const plusOne = csum(1)
Isso nos permite reescrever nossa construção com
map()
seguinte maneira:
[1, 2, 3].map(csum(1))
Agora, usando a mesma técnica,
isBiggerThan()
função
isBiggerThan()
. Se quiser, tente fazer você mesmo e continue lendo. Isso eliminará o uso de construções desnecessárias ao usar o método
filter()
. Primeiro, vamos trazer o código para este formulário:
const isBiggerThan = (threshold, int) => int > threshold [1, 2, 3, 4].filter(int => isBiggerThan(3, int))
Então, nos livrando de tudo que é supérfluo, obtemos o código que você já viu no começo deste material:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] .filter(isEven) .filter(isBiggerThan(3)) .map(plus(1)) .map(toChar) .filter(not(isVowel)) .join('')
Agora, consideramos duas regras simples que permitem escrever código no estilo discutido aqui.
Regra número 1
As duas construções a seguir são equivalentes:
[…].map(x => fnc(x)) […].map(fnc)
Regra número 2
Os retornos de chamada sempre podem ser reescritos para reduzir o número de argumentos usados para chamá-lo:
const fnc = (x, y, z) => … […].map(x => fnc(x, y, z)) const fnc = (y, z) => x => … […].map(fnc(y, z))
Se você mesmo escreveu a função
isBiggerThan()
, provavelmente já recorreu a essa transformação. Suponha que precisamos de números maiores que 3 para passar por um filtro, o que pode ser feito assim:
const isBiggerThan = (threshold, int) => int > threshold […].filter(int => isBiggerThan(3, int))
Agora reescrevemos a função
isBiggerThan()
para que possa ser usada no método
filter()
e não use a construção
int=>
:
const isBiggerThan = threshold => int => int > threshold […].map(isBiggerThan(3))
Exercício
Suponha que tenhamos o seguinte fragmento de código:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 keepGreatestChar('b', 'f') // 'f' // 'f' 'b'
Agora, com base na função
keepGreatestChar()
, crie a função
keepGreatestCharBetweenBAnd()
. Precisamos que, chamando-o, possamos passar apenas um argumento a ele, enquanto ele comparará o caractere passado a ele com o caractere
b
. Esta função pode ser assim:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const keepGreatestCharBetweenBAnd = char => keepGreatestChar('b', char) keepGreatestCharBetweenBAnd('a')
Agora escreva a função
greatestCharInArray()
, que, usando a função
keepGreatestChar()
no método array
reduce()
, permite procurar o caractere "maior" e não precisa de argumentos. Vamos começar com este código:
const keepGreatestChar = (char1, char2) => char1 > char2 ? char1 : char2 const greatestCharInArray = array => array.reduce((acc, char) => acc > char ? acc : char, 'a') greatestCharInArray(['a', 'b', 'c', 'd'])
Para resolver esse problema, implemente a função
creduce()
, que pode ser usada na função
greatestCharInArray()
, que permite, na aplicação prática dessa função, não transmitir nada a ele, exceto a matriz na qual o caractere com o maior código é encontrado.
A função
creduce()
deve ser universal o suficiente para poder ser usada para resolver qualquer problema que exija o uso dos recursos do método padrão da matriz
creduce()
. Em outras palavras, a função deve receber um retorno de chamada, um valor inicial e uma matriz para trabalhar. Como resultado, você deve obter uma função com a qual o seguinte fragmento de código funcionará:
const greatestCharInArray = creduce(keepGreatestChar, 'a') greatestCharInArray(['a', 'b', 'c', 'd'])
Sumário
Talvez agora você tenha uma pergunta sobre por que os métodos, processados de acordo com a metodologia apresentada aqui, têm nomes começando com o caractere
c
. O caractere
c
é um acrônimo para curry, e falamos sobre como as funções curry ajudam a melhorar a legibilidade do código. Deve-se notar que aqui não nos esforçamos por respeitar estritamente os princípios da programação funcional, mas acreditamos que a aplicação prática do que foi discutido aqui nos permite melhorar o código. Se o tópico sobre currying em JavaScript for interessante para você - é recomendável ler o capítulo 4
deste livro sobre programação funcional e, em geral, desde que você chegou a esse ponto - leia todo o livro. Além disso, se você é novo na programação funcional, consulte
este material inicial.
Caros leitores! Você usa função currying no desenvolvimento JavaScript?
