Entendendo Funções de Alta Ordem (Higher Order Functions)
- #Swift
E aí, pessoal!
Hoje irei falar sobre um assunto relativamente simples porém muito importante no universo de desenvolvimento iOS: Funções de Alta Ordem (Higher Order Functions).
Funções de alta ordem são funções que podem receber outras funções como argumentos e/ou retornar funções como resultados, e utilizam closures para viabilizar essas ações. Elas permitem que você escreva um código mais conciso e expressivo, além de facilitar a manipulação de coleções de dados, como arrays, matrizes e dicionários.
Neste artigo, irei abordar as funções de alta ordem que são utilizadas para lidar com arrays: sorted, map, filter, reduce e forEach.
Então bora entender isso melhor!
1. Sorted:
Podemos usar essa função de duas formas:
- Sorted():
Na imagem acima, temos um array do tipo Int, chamado numbers, que contém número de 1 a 10 dispostos de forma completamente aleatória.
Ao aplicarmos o .sorted() em numbers no objeto ascendingNumbers, vemos que ordenamos os itens desse array em ordem crescente, conforme demonstrado no print acima.
Porém podemos querer ordenar esses números de outra forma, certo? Digamos que queremos ordena-los de forma decrescente, vamos ver como ficaria:
- Sorted(by:):
Nesse caso, tivemos que usar o sorted(by:), o sorted by é uma closure na qual podemos definir qual a maneira que queremos organizar a nossa array. No exemplo dado, ao pedir como retorno a > b, determinei que numbers tivesse seus itens organizados de forma decrescente.
Talvez vocês estejam se perguntando de onde eu tirei esse a e b.
Bom, não é tão importante entender isso, mas tentarei explicar de forma simples: ao dizer que (a,b) -> Bool, o swift irá verificar em quais situações o número inteiro a é maior do que o número inteiro b, e sendo, ele irá ordenar que o número maior venha na frente do número menor.
Ou seja, o código comparará todos os itens de numbers entre si e irá organiza-los de forma que o s maiores números de numbers venham antes dos menores.
No exemplo, usei (a) e (b), mas poderia ter sido outro nome.
E se eu quiser que os números ímpares venham antes dos números pares? Simples!
Ao definir (a,b) -> Bool e pedir como retorno os casos em que a, quando divididos por 2, tenham resultado diferente de 0, estou determinando que os números ímpares venham antes dos pares, uma vez que (a) virá antes de (b) e (a) representa, nesse caso, os números ímpares.
2. Map:
Usamos o map quando queremos transformar os componentes da nossa array em um tipo diferente do que foi determinado inicialmente. Determinamos qual transformação será feita através da closure.
Vamos para um exemplo:
Neste exemplo, pedi para que o código pegasse item por item de numbers, simbolizados pela letra a, e transformasse-os em String. Ou seja, o map transformou uma array do tipo Int em uma array de String.
Uma outra forma de acessarmos os itens da array, ao invés de usar a letra “a”, como no exemplo acima, é utilizando o “$0”, conforme o exemplo abaixo:
O $0 simboliza o valor referente ao índex do loop, se está no primeiro loop, será índex 0, no seguinte, índex 1, e por aí vai.
3. Filter:
O método Filter irá ter como retorno apenas os itens da array que você quiser, basta que você especifique quais são esses itens na Closure.
Vamos entender melhor:
Neste exemplo, (a) representa os itens de nossa array, e pedi que o código filtrasse apenas os itens de numbers menores do que 5.
E, assim como fizemos antes, também poderia ter pedido para o código fazer isso usando o $0, como no exemplo abaixo:
4. Reduce:
Usando o Reduce você pode combinar todos os itens de uma coleção e obter como retorno um objeto de outro tipo. O reduce é a única Higher Order Function que não retornará uma array, mas sim um elemento. Na closure você deve determinar como você quer que essa combinação seja feita.
Vamos ao exemplo:
Ao aplicar a função reduce, preciso fornecer os seguintes parâmetros:
- initialResult: o método pede um valor inicial, no exemplo, o valor foi 0.
- partialResult: nome já sugerido pelo swift, mas pode ser qualquer outro nome. Esse objeto guarda o resultado parcial da operação que está sendo feita;
- indexNumber: nome que dei para os itens que existem em numbers e que variam de acordo com o índex do loop, poderia ser qualquer outro nome.
E pedimos como retorno dessa closure a soma entre partialResult e indexNumber.
Ou seja, o que aconteceu então é que o código pegou o valor inicial fornecido por mim (0), depois calculou a soma de todos os itens de numbers, guardando o resultado em partialResult, e somou o resultado dessa soma com o valor inicial fornecido por mim, gerando um resultado final de 55.
5. ForEach:
Usando o método forEach você consegue determinar que uma ação seja aplicada para cada item da array, ou seja, você consegue percorrer cada item da sua coleção e aplicar uma ação em todos eles.
Vamos ao exemplo:
No exemplo acima, criei uma variável para guardar um valor inicial (0) para a soma dos números pares (sumOfSquaresOfEvenNumbers).
Em seguida, apliquei o forEach em numbers, passando de parâmetro para closure a propriedade number, que simboliza os itens de numbers.
No código da closure, fiz uma lógica para conferir quais itens de numbers (number) poderiam ser divididos por 2 tendo como resultado dessa divisão o número 0, ou seja, quais são os números pares de numbers.
Sabendo quais são os números pares, criei a propriedade Square, para guardar como resultado a multiplicação dos números pares por eles mesmos (number * number)
Por fim, determinei que a variável sumOfSquaresOfEvenNumbers seria igual a soma dos quadrados dos números pares.