por Leandro Cissoto
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.
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.
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.
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.
A sintaxe da closure fica assim:
{ (parametros) -> tipo de retorno in declarações }
Exemplos de closures:
//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.
- Crie um novo playground com o nome de “closures” e limpe o arquivo.
- 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 }
- 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:
- 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.
- 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}
- 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
Observe que apenas os números pares foram inseridos no array.
- Vamos agora utilizar o método
reduce
para calcularmos a soma de todos os números ímpares. Para isso, vamos combinar ofilter
ereduce
: 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
- 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étodofilter
para selecionar apenas os números ímpares.
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á!