1. Introdução
Este codelab ensina sobre os tipos de função, como eles podem ser usados e a sintaxe específica para expressões lambda.
No Kotlin, as funções são consideradas construções de primeira classe. Isso significa que as funções podem ser tratadas como um tipo de dado. Você pode armazenar funções em variáveis, fazer a transmissão delas para outras funções como argumentos e retornar essas funções.
Assim como outros tipos de dados que você pode expressar com valores literais, como um tipo Int
de valor 10
e um tipo String
de valor "Hello"
, também é possível declarar literais de função, que são chamados de expressões lambda ou lambdas. Expressões lambda são usadas extensivamente no desenvolvimento Android e, de forma geral, na programação em Kotlin.
Pré-requisitos
- Familiaridade com a programação em Kotlin, incluindo funções, instruções
if/else
e nulidade.
O que você vai aprender
- Como definir uma função com sintaxe de lambda.
- Como armazenar funções em variáveis.
- Como transmitir funções como argumentos para outras funções.
- Como retornar funções de outras funções.
- Como usar tipos de função com nulidade.
- Como deixar as expressões lambda mais concisas.
- O que é uma função de ordem superior.
- Como usar a função
repeat()
.
O que é necessário
- Um navegador da Web com acesso ao Playground Kotlin.
2. Assistir ao vídeo de orientações (opcional)
Se você quiser acompanhar a conclusão deste codelab por um dos instrutores do curso, assista ao vídeo abaixo.
Recomendamos abrir o vídeo em tela cheia usando o ícone no canto do vídeo. Assim, você possa conferir o Playground Kotlin e o código com maior clareza.
Esta etapa é opcional. Você pode pular o vídeo e começar a seguir as instruções do codelab.
3. Armazenar uma função em uma variável
Até agora, você aprendeu a declarar funções com a palavra-chave fun
. Uma função declarada com fun
pode ser chamada, o que faz com que o código no corpo dela seja executado.
Por ser uma construção de primeira classe, as funções também são tipos de dados. Então, você pode armazenar funções em variáveis, fazer a transmissão delas para outras funções e retornar essas funções. Talvez você queira mudar o comportamento de um elemento do app no momento da execução ou aninhar funções de composição para criar layouts, como já feito em codelabs anteriores. Tudo isso é possível com expressões lambda.
Para explicar melhor, vamos usar a tradição doces ou travessuras (link em inglês), que ocorre no Halloween em vários países e na qual crianças fantasiadas pedem doces de porta em porta.
Armazene uma função em uma variável:
- Acesse o Kotlin Playground.
- Após a função
main()
, defina umatrick()
sem parâmetros e sem valor de retorno para mostrar a mensagem"No treats!"
(Sem doces!). A sintaxe é a mesma de outras funções mostradas em codelabs anteriores.
fun main() {
}
fun trick() {
println("No treats!")
}
- No corpo da função
main()
, crie uma variável chamadatrickFunction
e a defina como igual atrick
(travessuras). Os parênteses depois detrick
não são incluídos porque você não quer chamar a função, mas sim que ela seja armazenada em uma variável.
fun main() {
val trickFunction = trick
}
fun trick() {
println("No treats!")
}
- Execute o código. Ele gera um erro porque o compilador Kotlin reconhece
trick
como o nome da funçãotrick()
, mas espera que você chame a função em vez de a atribuir a uma variável.
Function invocation 'trick()' expected
Você tentou armazenar trick
na variável trickFunction
. No entanto, para se referir a uma função como um valor, é necessário usar o operador de referência de função (::
). A sintaxe é ilustrada nesta imagem:
- Para fazer referência à função como um valor, reatribua
trickFunction
a::trick
.
fun main() {
val trickFunction = ::trick
}
fun trick() {
println("No treats!")
}
- Execute o código para verificar se não há mais erros. Você vai receber um aviso informando que
trickFunction
não foi usada, mas isso será corrigido na próxima seção.
Redefinir a função com uma expressão lambda
As expressões lambda fornecem uma sintaxe concisa para definir uma função sem a palavra-chave fun
. Você pode armazenar uma expressão lambda diretamente em uma variável sem uma referência de função em outra função.
Antes do operador de atribuição (=
), adicione a palavra-chave val
ou var
seguida pelo nome da variável, que é o que você usa ao chamar a função. Depois do operador de atribuição (=
), está a expressão lambda, que consiste em um par de chaves que formam o corpo da função. A sintaxe está ilustrada nesta imagem:
Ao definir uma função com uma expressão lambda, você tem uma variável que se refere à função. Você também pode atribuir o valor a outras variáveis, como qualquer outro tipo, e chamar a função com o nome da nova variável.
Atualize o código para usar uma expressão lambda:
- Reprograme a função
trick()
com uma expressão lambda. O nometrick
agora se refere ao nome de uma variável. O corpo da função nas chaves agora é uma expressão lambda.
fun main() {
val trickFunction = ::trick
}
val trick = {
println("No treats!")
}
- Na função
main()
, remova o operador de referência de função (::
), já quetrick
agora se refere a uma variável em vez de um nome de função.
fun main() {
val trickFunction = trick
}
val trick = {
println("No treats!")
}
- Execute o código. Não há erros e você pode referenciar a função
trick()
sem o operador de referência de função (::
). Não há saída porque você ainda não chamou a função. - Na função
main()
, chame atrick()
, mas agora inclua os parênteses, como faria ao chamar qualquer outra função.
fun main() {
val trickFunction = trick
trick()
}
val trick = {
println("No treats!")
}
- Execute o código. O corpo da expressão lambda é executado.
No treats!
- Na função
main()
, chame a variáveltrickFunction
como se ela fosse uma função.
fun main() {
val trickFunction = trick
trick()
trickFunction()
}
val trick = {
println("No treats!")
}
- Execute o código. A função é chamada duas vezes: uma para a chamada de função
trick()
e outra para atrickFunction()
.
No treats! No treats!
As expressões lambda permitem criar variáveis que armazenam funções, fazer a chamada delas como funções e as armazenar em outras variáveis que podem ser chamadas como funções.
4. Usar funções como um tipo de dado
Você aprendeu em um codelab anterior que o Kotlin tem inferência de tipo. Ao declarar uma variável, geralmente não é necessário especificar explicitamente o tipo. No exemplo anterior, o compilador Kotlin conseguiu inferir que o valor de trick
era uma função. No entanto, se você quer especificar o tipo de parâmetro de função ou retorno, precisa saber a sintaxe para expressar os tipos de função. Os tipos de função consistem em um conjunto de parênteses com uma lista de parâmetros opcional, o símbolo ->
e um tipo de retorno. A sintaxe está ilustrada nesta imagem:
O tipo de dado da variável trick
que você declarou anteriormente seria () -> Unit
. Os parênteses estão vazios porque a função não tem parâmetros. O tipo de retorno é Unit
, porque a função não retorna nada. Se você tivesse uma função que usasse dois parâmetros Int
e retornasse um Int
, o tipo de dado dela seria (Int, Int) -> Int
.
Declare outra função com uma expressão lambda que especifique explicitamente o tipo da função:
- Depois da variável
trick
, declare uma variável chamadatreat
igual a uma expressão lambda com um corpo que mostra a mensagem"Have a treat!"
("Pegue um doce!").
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- Especifique o tipo de dado da variável
treat
como() -> Unit
.
val treat: () -> Unit = {
println("Have a treat!")
}
- Na função
main()
, chame atreat()
.
fun main() {
val trickFunction = trick
trick()
trickFunction()
treat()
}
- Execute o código. A função
treat()
se comporta como atrick()
. As duas variáveis têm o mesmo tipo de dado, mesmo que apenas a variáveltreat
o declare explicitamente.
No treats! No treats! Have a treat!
Usar uma função como um tipo de retorno
Uma função é um tipo de dado e, por isso, pode ser usada como qualquer outro. Você pode até mesmo retornar funções de outras funções. A sintaxe está ilustrada nesta imagem:
Crie uma função que retorne outra.
- Exclua o código da função
main()
.
fun main() {
}
- Após a função
main()
, defina umatrickOrTreat()
que aceite um parâmetroisTrick
do tipoBoolean
.
fun main() {
}
fun trickOrTreat(isTrick: Boolean): () -> Unit {
}
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- No corpo da função
trickOrTreat()
, adicione uma instruçãoif
que retorne a funçãotrick()
casoisTrick
sejatrue
e a funçãotreat()
casoisTrick
seja false.
fun trickOrTreat(isTrick: Boolean): () -> Unit {
if (isTrick) {
return trick
} else {
return treat
}
}
- Na função
main()
, crie uma variável chamadatreatFunction
e a atribua ao resultado da chamada detrickOrTreat()
, transmitindofalse
para o parâmetroisTrick
. Em seguida, crie uma segunda variável, chamadatrickFunction
, e a atribua ao resultado da chamada detrickOrTreat()
, desta vez transmitindotrue
para o parâmetroisTrick
.
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
}
- Chame
treatFunction()
e depoistrickFunction()
na próxima linha.
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- Execute o código. Você verá a resposta de cada função. Mesmo que não tenha chamado as funções
trick()
outreat()
diretamente, elas ainda podem ser chamadas, já que você armazenou os valores de retorno cada vez que chamou a funçãotrickOrTreat()
e usou as variáveistrickFunction
etreatFunction
para chamar as funções.
Have a treat! No treats!
Agora, você sabe como as funções podem retornar outras funções. Você também pode transmitir uma função como argumento para outra. Talvez você queira fornecer algum comportamento personalizado para que a função trickOrTreat()
faça algo além de retornar uma das duas strings. Uma função que usa outra como argumento permite transmitir uma função diferente sempre que ela é chamada.
Transmitir uma função para outra como argumento
Em algumas partes do mundo que celebram o Halloween, as crianças recebem trocados em vez de doces, ou recebem as duas coisas. Você vai modificar a função trickOrTreat()
para permitir que um presente diferente, representado por uma função, seja fornecido como argumento.
A função que trickOrTreat()
usa como parâmetro também precisa usar um parâmetro próprio. Ao declarar tipos de função, os parâmetros não são rotulados. Você só precisa especificar os tipos de dados de cada parâmetro, separados por vírgula. A sintaxe está ilustrada nesta imagem:
Quando você programa uma expressão lambda para uma função que usa um parâmetro, os parâmetros recebem nomes na ordem em que ocorrem. Os nomes dos parâmetros são listados depois das chaves iniciais, e cada nome é separado por uma vírgula. Uma seta (->
) separa os nomes dos parâmetros do corpo da função. A sintaxe está ilustrada nesta imagem:
Atualize a trickOrTreat()
para usar uma função como parâmetro:
- Depois do parâmetro
isTrick
, adicione umextraTreat
do tipo(Int) -> String
.
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
- No bloco
else
, antes da instruçãoreturn
, chameprintln()
, transmitindo uma chamada para a funçãoextraTreat()
. Transmita5
na chamada paraextraTreat()
.
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
if (isTrick) {
return trick
} else {
println(extraTreat(5))
return treat
}
}
- Agora, ao chamar
trickOrTreat()
, você precisa definir uma função com uma expressão lambda e a transmitir ao parâmetroextraTreat
. Na funçãomain()
, antes das chamadas para atrickOrTreat()
, adicione umacoins()
. A funçãocoins()
dá ao parâmetroInt
o nomequantity
e retorna umaString
. Perceba a ausência da palavra-chavereturn
, que não pode ser usada em expressões lambda. Em vez disso, o resultado da última expressão na função vai se tornar o valor de retorno.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- Depois da função
coins()
, adicione umacupcake()
, conforme mostrado. Nomeie aquantity
de parâmetrosInt
e a separe do corpo da função usando o operador->
. Você já pode transmitir a funçãocoins()
oucupcake()
para atrickOrTreat()
.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val cupcake: (Int) -> String = { quantity ->
"Have a cupcake!"
}
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- Na função
cupcake()
, remova o parâmetroquantity
e o símbolo->
. Eles não são usados, então podem ser omitidos.
val cupcake: (Int) -> String = {
"Have a cupcake!"
}
- Atualize as chamadas para a função
trickOrTreat()
. Para a primeira chamada, quandoisTrick
forfalse
, transmita a funçãocoins()
. Para a segunda chamada, quandoisTrick
fortrue
, transmita a funçãocupcake()
.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val cupcake: (Int) -> String = {
"Have a cupcake!"
}
val treatFunction = trickOrTreat(false, coins)
val trickFunction = trickOrTreat(true, cupcake)
treatFunction()
trickFunction()
}
- Execute o código. A função
extraTreat()
só é chamada quando o parâmetroisTrick
é definido como um argumentofalse
. Então, a saída inclui cinco moedas de 25 centavos, mas nenhum cupcake.
5 quarters Have a treat! No treats!
Tipos de função anuláveis
Assim como outros tipos de dados, os tipos de função podem ser declarados como anuláveis. Nesses casos, uma variável pode conter uma função ou ser null
.
Para declarar uma função como anulável, coloque o tipo de função entre parênteses, com um símbolo ?
depois do parêntese final. Por exemplo, se você quiser tornar o tipo () -> String
anulável, ele precisa ser declarado como um tipo (() -> String)?
. A sintaxe está ilustrada nesta imagem:
Torne o parâmetro extraTreat
anulável para que não seja necessário fornecer uma função extraTreat()
sempre que chamar a trickOrTreat()
:
- Mude o tipo de parâmetro
extraTreat
para(() -> String)?
.
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
- Modifique a chamada da função
extraTreat()
para usar uma instruçãoif
e chamar a função apenas se ela não for nula. A funçãotrickOrTreat()
agora terá esta aparência:
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
if (isTrick) {
return trick
} else {
if (extraTreat != null) {
println(extraTreat(5))
}
return treat
}
}
- Remova a função
cupcake()
e substitua o argumentocupcake
pornull
na segunda chamada para a funçãotrickOrTreat()
.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false, coins)
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Execute o código. A saída não será modificada. Agora que você pode declarar tipos de função como anuláveis, não é mais necessário transmitir uma função para o parâmetro
extraTreat
.
5 quarters Have a treat! No treats!
5. Programar expressões lambda com sintaxe abreviada
As expressões lambda oferecem várias maneiras de deixar seu código mais conciso. Algumas delas vão aparecer nesta seção, já que a maioria das expressões lambda que você encontra e programa têm sintaxe abreviada.
Omitir o nome do parâmetro
Ao programar a função coins()
, você declarou explicitamente o nome quantity
para o parâmetro Int
da função. No entanto, como você viu com a função cupcake()
, é possível omitir totalmente o nome do parâmetro. Quando uma função tem um único parâmetro e você não fornece um nome, o Kotlin atribui o nome it
implicitamente. Assim, você pode omitir o nome do parâmetro e o símbolo ->
, deixando suas expressões lambda mais concisas. A sintaxe está ilustrada nesta imagem:
Atualize a função coins()
para usar a sintaxe abreviada para parâmetros:
- Na função
coins()
, remova o nome do parâmetroquantity
e o símbolo->
.
val coins: (Int) -> String = {
"$quantity quarters"
}
- Mude o modelo de string
"$quantity quarters"
para se referir ao parâmetro único usando$it
.
val coins: (Int) -> String = {
"$it quarters"
}
- Execute o código. O Kotlin reconhece o nome do parâmetro
it
doInt
e ainda mostra o número de moedas de 25 centavos.
5 quarters Have a treat! No treats!
Transmitir uma expressão lambda diretamente para uma função
No momento, a função coins()
é usada apenas em um lugar. E se você pudesse simplesmente transmitir uma expressão lambda para a função trickOrTreat()
sem a necessidade de criar uma variável?
As expressões lambda são simplesmente literais de função, assim como 0
é um literal de número inteiro ou "Hello"
é um literal de string. Você pode transmitir uma expressão lambda diretamente para uma chamada de função. A sintaxe está ilustrada nesta imagem:
Modifique o código para remover a variável coins
:
- Mova a expressão lambda para que ela seja transmitida diretamente para a função
trickOrTreat()
na chamada. Você também pode condensar a expressão lambda em uma única linha.
fun main() {
val coins: (Int) -> String = {
"$it quarters"
}
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Remova a variável
coins
, que não será mais usada.
fun main() {
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Execute o código. A compilação e execução ainda funcionam como esperado.
5 quarters Have a treat! No treats!
Usar a sintaxe de lambda final
É possível usar outra opção abreviada para programar lambdas quando um tipo de função é o último parâmetro de uma função. Nesse caso, você pode colocar a expressão lambda após os parênteses para chamar a função. A sintaxe está ilustrada nesta imagem:
Isso deixa seu código mais legível, porque ele separa a expressão lambda dos outros parâmetros, mas não muda o que o código faz.
Atualize o código para usar a sintaxe de lambda final:
- Na variável
treatFunction
, mova a expressão lambda{"$it quarters"}
após os parênteses na chamada paratrickOrTreat()
.
val treatFunction = trickOrTreat(false) { "$it quarters" }
- Execute o código. Tudo continua funcionando.
5 quarters Have a treat! No treats!
6. Usar a função repeat()
Quando uma função retorna outra ou usa uma função como argumento, ela é chamada de função de ordem superior. A função trickOrTreat()
é um exemplo de função de ordem superior, já que usa uma função do tipo ((Int) -> String)?
como parâmetro e retorna outra do tipo () -> Unit
. O Kotlin oferece várias funções de ordem superior úteis que podem ser aproveitadas com seu novo conhecimento sobre lambdas.
A função repeat()
é uma dessas funções de ordem superior. A repeat()
é uma maneira concisa de expressar uma repetição for
com funções. Você vai usar essa e outras funções de ordem superior com frequência nas próximas unidades. A função repeat()
tem esta assinatura de função:
repeat(times: Int, action: (Int) -> Unit)
O parâmetro times
é o número de vezes que a ação precisa ocorrer. O parâmetro action
é uma função que usa um único parâmetro Int
e retorna um tipo Unit
. O parâmetro Int
da função action
é o número de vezes que a ação foi executada até o momento, como um argumento 0
na primeira iteração ou um 1
na segunda. Você pode usar a função repeat()
para repetir o código um número específico de vezes, semelhante a uma repetição for
. A sintaxe está ilustrada nesta imagem:
Em vez de chamar a função trickFunction()
apenas uma vez, você pode fazer isso várias vezes com a função repeat()
.
Atualize seu código de doces ou travessuras para ver a repeat()
em ação:
- Na função
main()
, chame arepeat()
entre as chamadas paratreatFunction()
etrickFunction()
. Transmita4
para o parâmetrotimes
e use a sintaxe de lambda final para a funçãoaction
. Não é necessário fornecer um nome para o parâmetroInt
da expressão lambda.
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
repeat(4) {
}
}
- Mova a chamada para a função
treatFunction()
na expressão lambda da funçãorepeat()
.
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
repeat(4) {
treatFunction()
}
trickFunction()
}
- Execute o código. A string
"Have a treat"
precisa ser mostrada quatro vezes.
5 quarters Have a treat! Have a treat! Have a treat! Have a treat! No treats!
7. Conclusão
Parabéns! Você aprendeu as noções básicas dos tipos de função e expressões lambda. Conhecer bem esses conceitos ajuda no aprendizado da linguagem Kotlin. O uso de tipos de função, funções de ordem superior e sintaxe abreviada também deixa o código mais conciso e fácil de ler.
Resumo
- As funções no Kotlin são construções de primeira classe e podem ser tratadas como tipos de dados.
- As expressões lambda fornecem uma sintaxe abreviada para programar funções.
- É possível transmitir tipos de função para outras funções.
- Você pode retornar um tipo de função de outra função.
- Uma expressão lambda retorna o valor da última expressão.
- Se um rótulo de parâmetro for omitido em uma expressão lambda com um único parâmetro, ele será referenciado com o identificador
it
. - É possível programar lambdas in-line sem um nome de variável.
- Se o último parâmetro de uma função for um tipo de função, você vai poder usar a sintaxe de lambda final para mover a expressão lambda após o último parêntese ao chamar uma função.
- As funções de ordem superior são aquelas que usam outras funções como parâmetros ou retornam uma função.
- A função
repeat()
é de ordem superior e funciona de maneira semelhante a uma repetiçãofor
.
Saiba mais
- Funções de ordem superior e lambdas (link em inglês)