1. Introducción
En este codelab, aprenderás sobre los tipos de funciones, cómo usarlos y la sintaxis específica de las expresiones lambda.
En Kotlin, las funciones se consideran construcciones de primera clase. Esto significa que las funciones se pueden tratar como un tipo de datos. Puedes almacenar funciones en variables, pasarlas a otras funciones como argumentos y mostrarlas desde otras funciones.
Al igual que otros tipos de datos que puedes expresar con valores literales, como un tipo Int
de un valor 10
y un tipo String
de un valor "Hello"
, también puedes declarar literales de funciones, que se llaman expresiones lambda o, simplemente, lambdas. Las expresiones lambda se usan en gran medida en el desarrollo de Android y, de manera más general, en la programación en Kotlin.
Requisitos previos
- Estar familiarizado con la programación en Kotlin, incluidas las funciones, las sentencias
if/else
y la nulabilidad
Qué aprenderás
- Cómo definir una función con sintaxis lambda
- Cómo almacenar funciones en variables
- Cómo pasar funciones como argumentos a otras funciones
- Cómo mostrar funciones de otras funciones
- Cómo usar tipos de funciones anulables
- Cómo hacer que las expresiones lambda sean más concisas
- Qué es una función de orden superior
- Cómo usar la función
repeat()
Requisitos
- Un navegador web con acceso a Playground de Kotlin
2. Mira el video con instrucciones para compilar (opcional)
Si quieres ver cómo uno de los instructores del curso completa el codelab, reproduce el siguiente video.
Se recomienda expandir el video a pantalla completa (con el ícono en la esquina del video) para que puedas ver Playground de Kotlin y el código con mayor claridad.
Este paso es opcional. También puedes omitir el video y comenzar con las instrucciones del codelab de inmediato.
3. Cómo almacenar una función en una variable
Hasta ahora, aprendiste a declarar funciones con la palabra clave fun
. Se puede llamar a una función declarada con la palabra clave fun
, lo que hace que se ejecute el código en el cuerpo de la función.
Como construcción de primera clase, las funciones también son tipos de datos, por lo que puedes almacenar funciones en variables, pasarlas a funciones y mostrarlas a partir de funciones. Quizás necesites la capacidad de cambiar el comportamiento de una parte de tu app en el tiempo de ejecución o anidar funciones de componibilidad para compilar diseños como lo hiciste en codelabs anteriores. Todo esto es posible gracias a las expresiones lambda.
Puedes verlo en acción con dulce o truco, que se refiere a la tradición de Halloween en muchos países cuando niños disfrazados van de puerta en puerta y preguntan: "¿Dulce o truco?", por lo general, a cambio de golosinas.
Almacena una función en una variable:
- Ve a Playground de Kotlin.
- Después de la función
main()
, define una funcióntrick()
sin parámetros y sin valor de retorno que imprima"No treats!"
. La sintaxis es la misma que la de otras funciones que viste en codelabs anteriores.
fun main() {
}
fun trick() {
println("No treats!")
}
- En el cuerpo de la función
main()
, crea una variable llamadatrickFunction
y establécela entrick
. No incluyas los paréntesis después detrick
, ya que quieres almacenar la función en una variable en lugar de llamar a la función.
fun main() {
val trickFunction = trick
}
fun trick() {
println("No treats!")
}
- Ejecuta tu código. Genera un error porque el compilador de Kotlin reconoce
trick
como el nombre de la funcióntrick()
, pero espera que la llames en lugar de asignarla a una variable.
Function invocation 'trick()' expected
Intentaste almacenar trick
en la variable trickFunction
. Sin embargo, para hacer referencia a una función como un valor, debes usar el operador de referencia de función (::
). La sintaxis se ilustra en esta imagen:
- Para hacer referencia a la función como un valor, reasigna
trickFunction
a::trick
.
fun main() {
val trickFunction = ::trick
}
fun trick() {
println("No treats!")
}
- Ejecuta tu código para verificar que no haya más errores. Verás una advertencia que indica que nunca se usa
trickFunction
, aunque ese error se corrigió en la siguiente sección.
Cómo volver a definir la función con una expresión lambda
Las expresiones lambda usan una sintaxis concisa para definir una función sin la palabra clave fun
. Puedes almacenar una expresión lambda directamente en una variable sin una referencia de función en otra función.
Antes del operador de asignación (=
), debes agregar la palabra clave val
o var
seguida del nombre de la variable, que es lo que usas cuando llamas a la función. Después del operador de asignación (=
) se encuentra la expresión lambda, que consiste en un par de llaves que forman el cuerpo de la función. La sintaxis se ilustra en esta imagen:
Cuando defines una función con una expresión lambda, tienes una variable que hace referencia a la función. También puedes asignar su valor a otras variables como cualquier otro tipo y llamar a la función con el nombre de la variable nueva.
Actualiza el código para usar una expresión lambda:
- Vuelve a escribir la función
trick()
con una expresión lambda. El nombretrick
ahora hace referencia al nombre de una variable. El cuerpo de la función en las llaves ahora es una expresión lambda.
fun main() {
val trickFunction = ::trick
}
val trick = {
println("No treats!")
}
- En la función
main()
, quita el operador de referencia de función (::
), ya quetrick
ahora hace referencia a una variable en lugar de a un nombre de función.
fun main() {
val trickFunction = trick
}
val trick = {
println("No treats!")
}
- Ejecuta tu código. No hay errores y puedes consultar la función
trick()
sin el operador de referencia de función (::
). No hay un resultado porque aún no llamaste a la función. - En la función
main()
, llama a la funcióntrick()
, pero esta vez incluye los paréntesis como lo harías cuando llamas a cualquier otra función.
fun main() {
val trickFunction = trick
trick()
}
val trick = {
println("No treats!")
}
- Ejecuta tu código. Se ejecuta el cuerpo de la expresión lambda.
No treats!
- En la función
main()
, llama a la variabletrickFunction
como si fuera una función.
fun main() {
val trickFunction = trick
trick()
trickFunction()
}
val trick = {
println("No treats!")
}
- Ejecuta tu código. Se llama a la función dos veces, una para la llamada a función
trick()
y otra para la llamada a funcióntrickFunction()
.
No treats! No treats!
Con las expresiones lambda, puedes crear variables que almacenen funciones, llamar a estas variables como funciones y almacenarlas en otras variables a las que puedes llamar como funciones.
4. Cómo usar funciones como un tipo de datos
En un codelab anterior, aprendiste que Kotlin tiene inferencia de tipo. Cuando declaras una variable, a menudo no es necesario que especifiques el tipo de forma explícita. En el ejemplo anterior, el compilador de Kotlin pudo inferir que el valor de trick
era una función. Sin embargo, si deseas especificar el tipo de parámetro de función o un tipo de datos que se muestra, debes conocer la sintaxis para expresar tipos de función. Los tipos de funciones consisten en un conjunto de paréntesis que contienen una lista de parámetros opcional, el símbolo ->
y un tipo de datos que se muestra. La sintaxis se ilustra en esta imagen:
El tipo de datos de la variable trick
que declaraste antes sería () -> Unit
. Los paréntesis están vacíos porque la función no tiene ningún parámetro. El tipo de datos que se muestra es Unit
porque la función no muestra nada. Si tienes una función que toma dos parámetros Int
y muestra un Int
, el tipo de datos será (Int, Int) -> Int
.
Declara otra función con una expresión lambda que especifique el tipo de función de forma explícita:
- Después de la variable
trick
, declara una variable llamadatreat
igual a una expresión lambda con un cuerpo que imprima"Have a treat!"
.
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- Especifica el tipo de datos de la variable
treat
como() -> Unit
.
val treat: () -> Unit = {
println("Have a treat!")
}
- En la función
main()
, llama a la funcióntreat()
.
fun main() {
val trickFunction = trick
trick()
trickFunction()
treat()
}
- Ejecuta el código. La función
treat()
se comporta como la funcióntrick()
. Ambas variables tienen el mismo tipo de datos, pero solo la variabletreat
lo declara explícitamente.
No treats! No treats! Have a treat!
Cómo usar una función como tipo de datos que se muestra
Una función es un tipo de datos, por lo que puedes usarla como cualquier otro tipo de datos. Incluso puedes mostrar funciones de otras funciones. La sintaxis se ilustra en esta imagen:
Crea una función que muestre una función.
- Borra el código de la función
main()
.
fun main() {
}
- Después de la función
main()
, define una funcióntrickOrTreat()
que acepte un parámetroisTrick
de tipoBoolean
.
fun main() {
}
fun trickOrTreat(isTrick: Boolean): () -> Unit {
}
val trick = {
println("No treats!")
}
val treat = {
println("Have a treat!")
}
- En el cuerpo de la función
trickOrTreat()
, agrega una sentenciaif
que muestre la funcióntrick()
siisTrick
estrue
y que muestre la funcióntreat()
siisTrick
es falso.
fun trickOrTreat(isTrick: Boolean): () -> Unit {
if (isTrick) {
return trick
} else {
return treat
}
}
- En la función
main()
, crea una variable llamadatreatFunction
y asígnala al resultado de la llamada atrickOrTreat()
. Para ello, pasafalse
para el parámetroisTrick
. Luego, crea una segunda variable, llamadatrickFunction
, y asígnala al resultado de la llamada atrickOrTreat()
. Esta vez, pasatrue
para el parámetroisTrick
.
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
}
- Llama a
treatFunction()
y, luego, atrickFunction()
en la siguiente línea.
fun main() {
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- Ejecuta tu código. Deberías ver el resultado de cada función. Aunque no llamaste a las funciones
trick()
otreat()
directamente, aún puedes llamarlas porque almacenaste los valores que se muestran cada vez que llamaste a la funcióntrickOrTreat()
, y llamaste a las funciones con las variablestrickFunction
ytreatFunction
.
Have a treat! No treats!
Ahora sabes cómo las funciones pueden mostrar otras funciones. También puedes pasar una función como argumento a otra función. Tal vez quieras brindar un comportamiento personalizado a la función trickOrTreat()
para que haga algo más que mostrar cualquiera de las dos strings. Una función que toma otra función como argumento te permite pasar una función diferente cada vez que se la llama.
Cómo pasar una función a otra como un argumento
En algunas partes del mundo que celebran Halloween, los niños reciben algo de dinero en lugar de dulces (o reciben ambos). Modificarás tu función trickOrTreat()
para permitir que un regalo adicional, representado por una función, se proporcione como argumento.
La función que trickOrTreat()
usa como parámetro también debe tomar un parámetro propio. Cuando declaras tipos de funciones, los parámetros no se etiquetan. Solo debes especificar los tipos de datos de cada parámetro, separados por coma. La sintaxis se ilustra en esta imagen:
Cuando escribes una expresión lambda para una función que toma un parámetro, los parámetros reciben nombres en el orden en que ocurren. Los nombres de los parámetros se muestran después de las llaves de apertura y cada nombre está separado por una coma. Una flecha (->
) separa los nombres de los parámetros del cuerpo de la función. La sintaxis se ilustra en esta imagen:
Actualiza la función trickOrTreat()
para que tome una función como parámetro:
- Después del parámetro
isTrick
, agrega un parámetroextraTreat
de tipo(Int) -> String
.
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
- En el bloque
else
, antes de la sentenciareturn
, llama aprintln()
y pasa una llamada a la funciónextraTreat()
. Pasa5
a la llamada aextraTreat()
.
fun trickOrTreat(isTrick: Boolean, extraTreat: (Int) -> String): () -> Unit {
if (isTrick) {
return trick
} else {
println(extraTreat(5))
return treat
}
}
- Ahora, cuando llames a la función
trickOrTreat()
, deberás definir una función con una expresión lambda y pasarla para el parámetroextraTreat
. En la funciónmain()
antes de las llamadas a la funcióntrickOrTreat()
, agrega una funcióncoins()
. La funcióncoins()
le asigna el nombrequantity
al parámetroInt
y muestra un objetoString
. Observarás la ausencia de la palabra clavereturn
, que no se puede usar en expresiones lambda. En cambio, el resultado de la última expresión en la función se convierte en el valor que se muestra.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false)
val trickFunction = trickOrTreat(true)
treatFunction()
trickFunction()
}
- Después de la función
coins()
, agrega una funcióncupcake()
como se muestra. Asigna el nombrequantity
al parámetroInt
y sepáralo del cuerpo de la función con el operador->
. Ahora puedes pasar la funcióncoins()
ocupcake()
a la funcióntrickOrTreat()
.
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()
}
- En la función
cupcake()
, quita el parámetroquantity
y el símbolo->
. Como no se usan, puedes omitirlos.
val cupcake: (Int) -> String = {
"Have a cupcake!"
}
- Actualiza las llamadas a la función
trickOrTreat()
. Para la primera llamada, cuandoisTrick
esfalse
, pasa la funcióncoins()
. Para la segunda llamada, cuandoisTrick
estrue
, pasa la funcióncupcake()
.
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()
}
- Ejecuta tu código. Solo se llama a la función
extraTreat()
cuando el parámetroisTrick
se establece en un argumentofalse
, por lo que el resultado incluye 5 trimestres, pero no magdalenas.
5 quarters Have a treat! No treats!
Tipos de funciones anulables
Al igual que otros tipos de datos, se pueden declarar los tipos de funciones como anulables. En estos casos, una variable podría contener una función o podría ser null
.
Para declarar una función como anulable, encierra el tipo de función entre paréntesis seguido de un símbolo ?
fuera del paréntesis de cierre. Por ejemplo, si deseas que el tipo () -> String
sea anulable, decláralo como un tipo (() -> String)?
. La sintaxis se ilustra en esta imagen:
Haz que el parámetro extraTreat
sea anulable para que no tengas que proporcionar una función extraTreat()
cada vez que llames a la función trickOrTreat()
:
- Cambia el tipo del parámetro
extraTreat
a(() -> String)?
.
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
- Modifica la llamada a la función
extraTreat()
para usar una sentenciaif
a fin de llamar a la función solo si no es nula. La funcióntrickOrTreat()
debería verse de la siguiente manera:
fun trickOrTreat(isTrick: Boolean, extraTreat: ((Int) -> String)?): () -> Unit {
if (isTrick) {
return trick
} else {
if (extraTreat != null) {
println(extraTreat(5))
}
return treat
}
}
- Quita la función
cupcake()
y, luego, reemplaza el argumentocupcake
pornull
en la segunda llamada a la funcióntrickOrTreat()
.
fun main() {
val coins: (Int) -> String = { quantity ->
"$quantity quarters"
}
val treatFunction = trickOrTreat(false, coins)
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Ejecuta tu código. El resultado no debería cambiar. Ahora que puedes declarar tipos de funciones como anulables, ya no necesitas pasar una función para el parámetro
extraTreat
.
5 quarters Have a treat! No treats!
5. Cómo escribir expresiones lambda con sintaxis abreviada
Las expresiones lambda sirven para que tu código sea más conciso. En esta sección, explorarás algunas de ellas, ya que la mayoría de las expresiones lambda que encuentras y escribes están escritas con sintaxis abreviada.
Cómo omitir el nombre del parámetro
Cuando escribiste la función coins()
, declaraste de forma explícita el nombre quantity
para el parámetro Int
de la función. Sin embargo, como viste con la función cupcake()
, puedes omitir el nombre del parámetro por completo. Cuando una función tiene un solo parámetro y no proporcionas un nombre, Kotlin le asigna de forma implícita el nombre it
, de manera que puedes omitir el nombre del parámetro y el símbolo ->
, lo que hace que tus expresiones lambda sean más concisas. La sintaxis se ilustra en esta imagen:
Actualiza la función coins()
para usar la sintaxis abreviada para los parámetros:
- En la función
coins()
, quita el nombre del parámetroquantity
y el símbolo->
.
val coins: (Int) -> String = {
"$quantity quarters"
}
- Cambia la plantilla de string
"$quantity quarters"
para hacer referencia al parámetro único con$it
.
val coins: (Int) -> String = {
"$it quarters"
}
- Ejecuta tu código. Kotlin reconoce el nombre del parámetro
it
del parámetroInt
y aún imprime la cantidad de trimestres.
5 quarters Have a treat! No treats!
Cómo pasar una expresión lambda directamente a una función
Por el momento, la función coins()
solo se usa en un lugar. ¿Qué ocurriría si pudieras pasar una expresión lambda directamente a la función trickOrTreat()
sin tener que crear una variable primero?
Las expresiones lambda son solo literales de función, al igual que 0
es un literal de número entero o "Hello"
es un literal de cadena. Puedes pasar una expresión lambda directamente a una llamada a función. La sintaxis se ilustra en esta imagen:
Modifica el código para que puedas quitar la variable coins
:
- Mueve la expresión lambda para que se pase directamente a la llamada a la función
trickOrTreat()
. También puedes condensar la expresión lambda en una sola línea.
fun main() {
val coins: (Int) -> String = {
"$it quarters"
}
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Quita la variable
coins
porque ya no se usa.
fun main() {
val treatFunction = trickOrTreat(false, { "$it quarters" })
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
}
- Ejecuta el código. Se compila y ejecuta según lo esperado.
5 quarters Have a treat! No treats!
Cómo usar la sintaxis de expresión lambda al final
Puedes usar otra opción abreviada para escribir lambdas cuando un tipo de función es el último parámetro de una función. De ser así, puedes colocar la expresión lambda después del paréntesis de cierre para llamar a la función. La sintaxis se ilustra en esta imagen:
Esto hace que el código sea más legible porque separa la expresión lambda de los otros parámetros, pero no cambia lo que hace el código.
Actualiza el código para usar la sintaxis lambda final:
- En la variable
treatFunction
, mueve la expresión lambda{"$it quarters"}
después del paréntesis de cierre en la llamada atrickOrTreat()
.
val treatFunction = trickOrTreat(false) { "$it quarters" }
- Ejecuta tu código. ¡Todo funciona!
5 quarters Have a treat! No treats!
6. Cómo usar la función repeat()
Cuando una función muestra una función o toma una función como argumento, se denomina función de orden superior. La función trickOrTreat()
es un ejemplo de una función de orden superior porque toma una función de tipo ((Int) -> String)?
como parámetro y muestra una función de tipo () -> Unit
. Kotlin ofrece varias funciones útiles de orden superior, que puedes aprovechar con todo lo que aprendiste recientemente sobre lambdas.
La función repeat()
es una de esas funciones de orden superior. La función repeat()
es una forma concisa de expresar un bucle for
con funciones. Usarás esta y otras funciones de orden superior con frecuencia en unidades posteriores. La función repeat()
tiene la siguiente firma de función:
repeat(times: Int, action: (Int) -> Unit)
El parámetro times
es la cantidad de veces que debe ocurrir la acción. El parámetro action
es una función que toma un solo parámetro Int
y muestra un tipo Unit
. El parámetro Int
de la función action
es la cantidad de veces que se ejecutó la acción hasta el momento, como un argumento 0
para la primera iteración o un argumento 1
para la segunda. Puedes usar la función repeat()
para repetir el código una cantidad especificada de veces, de manera similar a un bucle for
. La sintaxis se ilustra en esta imagen:
En lugar de llamar a la función trickFunction()
solo una vez, puedes llamarla varias veces con la función repeat()
.
Actualiza el código de dulce o truco para ver la función repeat()
en acción:
- En la función
main()
, llama a la funciónrepeat()
entre las llamadas atreatFunction()
ytrickFunction()
. Pasa4
para el parámetrotimes
y usa la sintaxis lambda al final de la funciónaction
. No es necesario que proporciones un nombre para el parámetroInt
de la expresión lambda.
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
treatFunction()
trickFunction()
repeat(4) {
}
}
- Mueve la llamada a la función
treatFunction()
en la expresión lambda de la funciónrepeat()
.
fun main() {
val treatFunction = trickOrTreat(false) { "$it quarters" }
val trickFunction = trickOrTreat(true, null)
repeat(4) {
treatFunction()
}
trickFunction()
}
- Ejecuta tu código. La string
"Have a treat"
debería imprimirse cuatro veces.
5 quarters Have a treat! Have a treat! Have a treat! Have a treat! No treats!
7. Conclusión
¡Felicitaciones! Aprendiste los conceptos básicos de los tipos de funciones y las expresiones lambda. Estar familiarizado con estos conceptos te ayudará a obtener más información sobre el lenguaje Kotlin. El uso de tipos de funciones, funciones de orden superior y sintaxis abreviada también hace que tu código sea más conciso y fácil de leer.
Resumen
- Las funciones en Kotlin son construcciones de primer nivel y se pueden tratar como tipos de datos.
- Las expresiones lambda proporcionan una sintaxis abreviada para escribir funciones.
- Puedes pasar tipos de funciones a otras funciones.
- Puedes mostrar un tipo de función desde otra.
- Una expresión lambda muestra el valor de la última expresión.
- Si se omite una etiqueta de parámetro en una expresión lambda con un solo parámetro, se hace referencia a ella con el identificador
it
. - Las expresiones lambda se pueden escribir intercaladas sin un nombre de variable.
- Si el último parámetro de una función es un tipo de función, puedes usar la sintaxis lambda al final para mover la expresión lambda después del último paréntesis cuando llamas a una función.
- Las funciones de orden superior son funciones que toman otras funciones como parámetros o muestran una función.
- La función
repeat()
es una función de orden superior que funciona de manera similar a un buclefor
.