Quaddro + MacMagazine: Swift na prática #7 — Closures

por Leandro Cissoto

Publicidade

E chegamos ao nosso sétimo artigo sobre Swift na parceria Quaddro + MacMagazine. Hoje vamos falar das closures, assunto que assusta alguns e alegra outros — mas vocês verão que não é tão complicado quanto parece.

Ícone da Swift

Lembramos que, para testar os conceitos aqui apresentados, recomendamos o uso do Xcode 6 ou superior. Se você não possui um Mac ou não quer instalar o Xcode, é possível utilizar uma ferramenta online que permite escrever em Swift diretamente pelo navegador.


Ícone do app Xcode

Xcode

de Apple

Compatível com Macs
Versão 11.6 (8.1 GB)
Requer o macOS 10.15.2 ou superior

Teoria

As closures são utilizadas para a criação de funções inline. Com elas podemos criar blocos de códigos que podem atuar como variáveis ou funções. Para quem conhece Objective-C, podemos dizer que as closures são equivalentes aos blocos ou, fazendo uma analogia com outras linguagens de programação, podemos dizer que são como callbacks e lambdas.

Como fazemos com funções e métodos, as closures podem receber parâmetros (argumentos) e também possuir um retorno de dados.

Publicidade

Nota: void é um tipo! Portanto, mesmo que a closure não retorne nada, você deverá utilizar o tipo void.

As closures normalmente estão entre chaves {} e são definidas por uma função do tipo ()->(), onde -> separa os argumentos do retorno, seguido da palavra reservada in, que separa o cabeçalho da closure de seu corpo.

Publicidade

A sintaxe da closure fica assim:

{ (parametros) -> tipo de retorno in
	declarações
}

Exemplos de closures:

Publicidade
//Declaramos uma função que recebe uma função como argumento
func multiplicacao(numero: Int, funcaoMult: Int -> Int)-> Int{
    return funcaoMult(numero)
}

//Utilizamos a closure para executarmos o bloco
multiplicacao(30, {valor in
    valor * 3
})

Podemos obter o mesmo resultado referenciando o argumento através do caractere reservado $ seguido do número do argumento. Utilizando a função declarada no exemplo anterior, vamos usar uma closure através da referência de seu argumento:

multiplicacao(30, {$0 * 3})

O resultado deste exemplo é idêntico ao do anterior, mesmo a closure sendo utilizada de forma diferente. Dá para ir além: se uma closure for o último argumento de uma função, os parênteses podem ser omitidos.

Exemplo:

multiplicacao(30){$0 * 3}

MAP

O método map foi feito para simplificar a nossa vida na hora transformar elementos de um array. Vou dar um exemplo: eu tenho um array valores do tipo float e gostaria de converte-los para o tipo string, adicionando a moeda corrente antes do valor. Algo mais ou menos assim:

[100.4, 300.9, 538.7, 3247.9] viraria ["R$ 100.4", "R$ 300.9", "R$ 538.7", "R$ 3247.9"]

Poderíamos fazer isso de varias forma. O jeito menos funcional seria:

var dinheiroArray = [100.4, 300.9, 538.7, 3247.9]

var dinheiroString: [String] = []

for dinheiro in dinheiroArray{
    dinheiroString.append("R$ \(dinheiro)")
}

Atinge o objetivo? Atinge. Mas se pode ficar melhor, por que não? Que tal utilizarmos map para fazer a mesma coisa?

dinheiroString = dinheiroArray.map({"R$ \($0)"})

Ou então:

dinheiroString = dinheiroArray.map({dinheiro in "R$ \(dinheiro)"})

Ambas as formas vão resolver o nosso problema. Perceba que a função map percorre todo o array e retorna o bloco definido pela closure item por item.

FILTER

Outro método espetacular que utiliza closures é o filter, que, como o próprio nome diz, serve para filtrar elementos a partir de uma condição. Utilizando o mesmo array do exercício anterior, vamos filtrar alguns resultados, começando pelo jeito tradicional e nem um pouco funcional:

var dinheiroFiltrado: [Double] = []

for dinheiro in dinheiroArray{   
    if(dinheiro > 301){
        dinheiroFiltrado.append(dinheiro)
    }  
}

Dá para fazer isso mais fácil?! Dá, sim, usando filter:

dinheiroFiltrado = dinheiroArray.filter({$0 > 301})

O método filter recebe uma expressão que verifica, item por item do array, se ela é válida ou não, e retorna o elemento — caso seja.

REDUCE

Usamos o método reduce para resolver os problemas de combinarmos o valor de um array em um único valor. Ainda utilizando o valor do exercício anterior, vamos realizar a soma de todos os elementos do array, começando pela forma menos funcional.

var soma = 0

for dinheiro in dinheiroArray{
    soma = soma + dinheiro
}

Agora vamos ver como fica se utilizarmos o método reduce.

soma = dinheiroArray.reduce(0,combine: {$0 + $1})

Isso também pode ser feito desta forma:

soma = dinheiroArray.reduce(0){$0 + $1}

Ou desta outra:

soma = dinheiroArray.reduce(0, combine:+)

O reduce talvez seja o mais complicado das três de se entender. Vou tentar clarear as coisas: quando chamamos o método reduce, passamos um parâmetro inicial de valor 0 (que é o valor inicial atribuído à variável soma). Depois, dentro da closure, dizemos que vamos somar a este valor inicial ($0) o valor da posição atual do array ($1).

Esses métodos (map, filter e reduce) fazem parte da classe array e, com as mudanças da Swift 2, serão uma extensão dos protocolos da SequenceType.

Exercício guiado

Agora que já aprendemos alguns conceitos de closures, vamos fazer um exercício guiado para consolidar ainda mais o conteúdo.

  1. Crie um novo playground com o nome de “closures” e limpe o arquivo.

Quaddro + MacMagazine - Closures

  1. Vamos criar uma função de soma que retorna.
//Função que retorna a área do quadrado
func quadrado(a: Float)->Float{
    return a * a
}

//Função que retorna a área do cubo
func cubo(a: Float)->Float{
    return a * a * a
}

Quaddro + MacMagazine - Closures

  1. Vamos agora criar uma outra função que receberá uma closure como parâmetro. Ela será responsável por fazer a média das áreas recebidas.
//Criando nossa função que recebe uma closure
func media(ladoA: Float, ladoB: Float, forma: (Float -> Float))->Float{
    
    return (forma(ladoA) + forma(ladoB)) / 2
}

Até aqui, o nosso código está assim:

Quaddro + MacMagazine - Closures

  1. Vamos agora passar alguns valores e testar nossa função.
//Utilizando nossas funções
media(10, 20, cubo)
media(10, 10, quadrado)

Perceba que usamos a closure de maneira explícita. Toda vez que chamamos a função média, passamos dois parâmetros definidos e uma função, que internamente realizará o cálculo.

Quaddro + MacMagazine - Closures

  1. Podemos, em vez de passarmos uma função (como as nossas funções quadrado e cubo), criar a nossa própria execução dentro da closure. Insira o código abaixo:
media(2, 4, {(valor: Float)-> Float in
    return valor * valor
})

Podemos ainda simplificar mais a operação anterior, fazendo referências aos parâmetros. Teste desta forma:

media(2, 4, {$0 * $0})

Ou desta:

media(2, 4){$0 * $0}

Quaddro + MacMagazine - Closures

  1. Vamos utilizar a função filter para dizermos se um número é par ou ímpar. Utilizaremos o resto da divisão para fazê-lo.
//Criamos um array
let numeros: [Int] = [10, 32, 1, 15, 40, 10329, 198, 947]

//Filtramos apenas os valores pares dentro do array numerosPares
var numerosPares = numeros.filter({(valor) in valor % 2 == 0})

numerosPares

Quaddro + MacMagazine - Closures

Observe que apenas os números pares foram inseridos no array.

  1. Vamos agora utilizar o método reduce para calcularmos a soma de todos os números ímpares. Para isso, vamos combinar o filter e reduce: o primeiro filtrará os números e o segundo realizará a soma. Farei isso utilizando a referência aos parâmetros.
var somaImpar = numeros.filter({$0 % 2 != 0})
                       .reduce(0, combine: {$0 + $1})

somaImpar

Quaddro + MacMagazine - Closures

  1. Agora vamos utilizar a função map para acrescentar “1” em todos os números ímpares — transformando-os em números pares — carregando essa informações em um novo array. Utilizarei novamente o método filter para selecionar apenas os números ímpares.

Quaddro + MacMagazine - Closures

Deu para perceber como filter, map e reduce são poderosos, não é mesmo? São ótimos recursos para utilizarmos cada vez mais uma programação funcional em nossas aplicações.

Desafio diferença

Criar uma função que receberá dois atributos do tipo inteiro e uma função que receberá dois inteiros e retorna uma string. Essa closure terá que verificar se há diferença entre os dois números e retornar uma frase de acordo com as regras abaixo:

  • Caso o numero A seja maior, retornar:
"Numero A é maior e tem uma diferença de X para B" -> Sendo X o valor da diferença
  • Caso sejam iguais:
"Os números são iguais, não há diferença"
  • Caso o número B seja diferente:
"Numero B é maior e tem uma diferença de X para A"

·   ·   ·

Lembramos que o resultado dos desafios pode ser enviado para mm@quaddro.com.br. E no nosso GitHub, você encontrará os exercícios de todos os artigos — bem como as soluções dos desafios.

No próximo artigo, falaremos de type casting e optionals. Até lá!

Posts relacionados

Comentários

Carregando os comentários…