Segredos da culinária JavaScript: especiarias

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('') // 'fhjl' 
"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) // 3 

Nós a reescrevemos, dando à nova função o nome csum() :

 const csum = a => b => a + b csum(1)(2) // 3 

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) // b => 1 + b 

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) // 3 

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) // [2, 3, 4] 

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)) // [2, 3, 4] 

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) // 2 

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) // 2 

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)) // [2, 3, 4] 

Aqui está o segundo:

 [1, 2, 3].map(plusOne) // [2, 3, 4] 

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)) // [2, 3, 4] 

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('') // 'fhjl' 

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') // 'b' //   'b'   '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']) // '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']) // '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?

Source: https://habr.com/ru/post/pt415059/


All Articles