In de programmeertaal Kotlin zijn de standaard hogere-orde functies apply, also, let en run geïntroduceerd om chainable configuratie van objecten en lokale wijzigingen te vereenvoudigen met een minimum aan boilerplate. Deze functies vergemakkelijken het werken met veranderlijke en onveranderlijke objecten, stellen ons in staat om ketens van transformaties beknopt uit te drukken, en verminderen een deel van de problemen met de tijdsgebied van variabelen.
Deze functies zijn overgenomen van het builder-patroon en benaderingen van fluent interfaces. Hun ontstaan is te danken aan de drang om de code schoner te maken en deze te ontdoen van overmatige declaraties van hulpparameters.
De gebruikelijke aanpak vereist herhaaldelijke verwijzingen naar het object of het extraheren van tijdelijke variabelen. Dit verlaagd de leesbaarheid en vergroot het risico op fouten:
val user = User() user.name = "Alex" user.age = 26 user.isActive = true
Het gebruik van de functies apply, also, let, run verhoogt de expressiviteit:
val user = User().apply { name = "Alex" age = 26 isActive = true }
Korte beschrijvingen:
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 geconfigureerd: $it") }) val emailLength = configuredUser.email?.let { it.length } ?: 0 val description = configuredUser.run { "$name ($age)" }
Belangrijke kenmerken:
Kan de functie let het object waar het mee werkt veranderen?
De functie let is niet bedoeld om het object te veranderen. Het dient eerder om transformatie toe te passen op een waarde en retourneert het resultaat. Houd er rekening mee dat binnen let in plaats van this it beschikbaar is.
val upperName = user.name.let { it.uppercase() } // let verandert user niet
Wat is het verschil tussen apply en run?
Beiden werken met de context this binnen de lambda; het verschil ligt in de geretourneerde waarde:
// apply val building = StringBuilder().apply { append("start-") append("end") } // building is een StringBuilder // run val result = StringBuilder().run { append("start-") append("end") toString() } // result is een String
Kan men apply en let nestelen? Zo ja, wanneer is dat gerechtvaardigd?
Ja, het nestelen wordt zachtjes aangeraden voor aggregatie of stapsgewijze configuratie van objecten, vooral bij het werken met nullable:
val userInfo = user?.apply { isActive = true }?.let { "${it.name} is actief: ${it.isActive}" }
Overal in de code worden let gebruikt voor het wijzigen van het object, apply en let worden vermengd — uiteindelijk verandert het object niet waar verwacht werd, en wordt de waarde verloren in ketens.
Voordelen:
Nadelen:
apply en also worden gebruikt voor configuratie en logging, let alleen voor het werken met nullable en verkrijgen van resultaat, run — voor ketens van transformaties die een nieuwe waarde vereisen.
Voordelen:
Nadelen: