En el lenguaje Kotlin, las funciones estándar de orden superior apply, also, let y run surgieron con el objetivo de simplificar la configuración encadenable de objetos y cambios locales con la mínima cantidad de boilerplate. Estas funciones facilitan el trabajo con objetos mutables e inmutables, permiten expresar de manera concisa las cadenas de transformaciones y también eliminan parte de los problemas con los ámbitos temporales de las variables.
Estas funciones se tomaron del patrón builder y de los enfoques de interfaces fluentes. Su aparición se debe al deseo de hacer el código más limpio y liberar de la declaración excesiva de variables auxiliares.
El enfoque común requiere múltiples referencias al objeto o la extracción de variables temporales. Esto reduce la legibilidad y aumenta el riesgo de errores:
val user = User() user.name = "Alex" user.age = 26 user.isActive = true
El uso de las funciones apply, also, let, run aumenta la expresividad:
val user = User().apply { name = "Alex" age = 26 isActive = true }
Descripciones breves:
data class User(var name: String = "", var age: Int = 0, var isActive: Boolean = false) val configuredUser = User().apply { name = "Alice" age = 30 isActive = true } debugUser(configuredUser.also { println("User is configured: $it") }) val emailLength = configuredUser.email?.let { it.length } ?: 0 val description = configuredUser.run { "$name ($age)" }
Características clave:
¿Puede la función let modificar el objeto con el que trabaja?
La función let no está destinada a modificar el objeto. Más bien, sirve para aplicar una transformación al valor y devolver el resultado. Hay que recordar que dentro de let, en lugar de this, se tiene acceso a it.
val upperName = user.name.let { it.uppercase() } // let no modifica user
¿Cuál es la diferencia entre apply y run?
Ambas trabajan con el contexto this dentro de la lambda, la diferencia está en el valor de retorno:
// apply val building = StringBuilder().apply { append("start-") append("end") } // building es StringBuilder // run val result = StringBuilder().run { append("start-") append("end") toString() } // result es String
¿Se puede anidar el uso de apply y let? Si es así, ¿cuándo es justificable?
Sí, la anidación se recomienda suavemente para la agregación o configuración paso a paso de objetos, especialmente al trabajar con valores nullable:
val userInfo = user?.apply { isActive = true }?.let { "${it.name} is active: ${it.isActive}" }
Se utilizan let en todo el código para modificar el objeto, se mezclan apply y let, como resultado el objeto no se modifica donde se esperaba y su valor se pierde en las cadenas.
Ventajas:
Desventajas:
Se utilizan apply y also para configuración y registro, let solo para trabajar con nullable y obtener el resultado, run para cadenas de transformaciones que requieren calcular un nuevo valor.
Ventajas:
Desventajas: