Nel linguaggio Kotlin, le funzioni di ordine superiore standard apply, also, let e run sono state introdotte per semplificare la configurazione concatenabile degli oggetti e le modifiche locali con il minor numero possibile di boilerplate. Queste funzioni facilitano il lavoro con oggetti mutabili e immutabili, permettono di esprimere in modo conciso le catene di trasformazioni e alleviano alcune problematiche legate ai contesti di visibilità temporali delle variabili.
Queste funzioni sono state ispirate dal pattern builder e dagli approcci fluency interface. La loro introduzione è dovuta alla volontà di rendere il codice più pulito e liberarlo dalla dichiarazione eccessiva di variabili ausiliarie.
L'approccio comune richiede ripetuti accessi all'oggetto o l'estrazione di variabili temporanee. Questo riduce la leggibilità e aumenta il rischio di errori:
val user = User() user.name = "Alex" user.age = 26 user.isActive = true
Usare le funzioni apply, also, let, run aumenta l'espressività:
val user = User().apply { name = "Alex" age = 26 isActive = true }
Descrizioni brevi:
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)" }
Caratteristiche principali:
La funzione let può modificare l'oggetto con cui lavora?
La funzione let non è destinata a modificare l'oggetto. È più utile per applicare trasformazioni a un valore e restituisce il risultato. È importante notare che all'interno di let, al posto di this è disponibile it.
val upperName = user.name.let { it.uppercase() } // let non modifica user
Qual è la differenza tra apply e run?
Entrambe lavorano con il contesto this all'interno della lambda, la differenza è nel valore restituito:
// apply val building = StringBuilder().apply { append("start-") append("end") } // building è un StringBuilder // run val result = StringBuilder().run { append("start-") append("end") toString() } // result è una String
È possibile annidare l'uso di apply e let? Se sì, quando è giustificato?
Sì, l'annidamento è gentilmente raccomandato per l'aggregazione o la configurazione passo passo degli oggetti, specialmente quando si lavora con nullable:
val userInfo = user?.apply { isActive = true }?.let { "${it.name} is active: ${it.isActive}" }
Viene usato ovunque let per modificare l'oggetto, si mescolano apply e let — alla fine l'oggetto non cambia dove ci si aspettava, e il suo valore si perde nelle catene.
Vantaggi:
Svantaggi:
Vengono usati apply e also per la configurazione e il logging, let solo per lavorare con nullable e ottenere risultati, run — per catene di trasformazioni che richiedono calcolo di un nuovo valore.
Vantaggi:
Svantaggi: